lp1942647 Provide Warning when deleting Term linked to Courses
authorKyle Huckins <khuckins@catalyte.io>
Tue, 15 Mar 2022 02:20:40 +0000 (02:20 +0000)
committerMichele Morgan <mmorgan@noblenet.org>
Fri, 17 Jun 2022 18:40:46 +0000 (14:40 -0400)
- Separates out Course Term Grid to its own component
- Adds retrieval service function for Course/Term Maps based on Term ID
- Displays Confirmation dialog when deleting terms on the term grid if term is mapped to a course

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>
 Changes to be committed:
modified:   Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.html
modified:   Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.html
new file:   Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.ts
modified:   Open-ILS/src/eg2/src/app/staff/share/course.service.ts

Signed-off-by: Beth Willis <willis@noblenet.org>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/course.service.ts

index 6fb0972..bd9a39e 100644 (file)
@@ -51,7 +51,7 @@
   <li ngbNavItem>
     <a ngbNavLink i18n>Terms</a>
     <ng-template ngbNavContent>
-      <eg-admin-page idlClass="acmt" [defaultNewRecord]="defaultTerm" fieldOrder="name,owning_lib,start_date,end_date"></eg-admin-page>
+      <eg-course-term-grid></eg-course-term-grid>
     </ng-template>
   </li>
   <li ngbNavItem>
index 149a39a..b7318a4 100644 (file)
@@ -12,6 +12,7 @@ import {MarcSimplifiedEditorModule} from '@eg/staff/share/marc-edit/simplified-e
 import {PatronModule} from '@eg/staff/share/patron/patron.module';
 import {CourseTermMapComponent} from './course-term-map.component';
 import {CourseTermMapGridComponent} from './course-term-map-grid.component';
+import {TermListComponent} from './course-term-grid.component';
 
 @NgModule({
   declarations: [
@@ -20,7 +21,8 @@ import {CourseTermMapGridComponent} from './course-term-map-grid.component';
     CourseAssociateMaterialComponent,
     CourseAssociateUsersComponent,
     CourseTermMapComponent,
-    CourseTermMapGridComponent
+    CourseTermMapGridComponent,
+    TermListComponent
   ],
   imports: [
     StaffCommonModule,
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.html
new file mode 100644 (file)
index 0000000..33b182c
--- /dev/null
@@ -0,0 +1,44 @@
+<eg-string #successString i18n-text text="{{tableName}} Update Succeeded"></eg-string>
+<eg-string #createString i18n-text text="{{tableName}} Was Created Successfully"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Deletion of {{tableName}} failed or was not allowed"></eg-string>
+<eg-string #deleteSuccessString i18n-text text="Deletion of {{tableName}} was successful"></eg-string>
+<eg-string #deleteLinkedTermWarningString i18n-text text="Warning: One or more courses are linked to this Term. Are you sure you want to delete {{termToDelete}}?"></eg-string>
+
+<ng-container>
+<div class="row">
+    <div class="col-lg-6">
+      <eg-org-family-select
+        [limitPerms]="['MANAGE_RESERVES']"
+        [selectedOrgId]="defaultOuId"
+        [(ngModel)]="searchOrgs"
+        (ngModelChange)="grid.reload()">
+      </eg-org-family-select>
+    </div>
+</div>
+<hr/>
+
+<div class="w-100 mt-2 mb-2">
+    <eg-grid #grid idlClass={{idlClass}}
+      [dataSource]="grid_source"
+      [sortable]="true">
+      <eg-grid-toolbar-button
+        label="Create {{tableName}}" (onClick)="createNew()" i18n-label>
+      </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-column label="ID" path="id" [index]=true [hidden]="true" i18n-label></eg-grid-column>
+      <eg-grid-column label="Name" name="name" i18n-label></eg-grid-column> 
+    </eg-grid>
+</div>
+</ng-container>
+
+<eg-fm-record-editor #editDialog
+  idlClass="acmt"
+  fieldOrder="name,owning_lib,start_date,end_date"
+  [preloadLinkedValues]="true"
+  hiddenFields="id,is_archived">
+</eg-fm-record-editor>
+
+<eg-confirm-dialog dialogBody="{{deleteLinkedTermWarningString.text}}" #deleteLinkedTermWarning></eg-confirm-dialog>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-grid.component.ts
new file mode 100644 (file)
index 0000000..9b6fb70
--- /dev/null
@@ -0,0 +1,186 @@
+import {Component, Input, ViewChild, OnInit, AfterViewInit} from '@angular/core';
+import {Router} from '@angular/router';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {CourseService} from '@eg/staff/share/course.service';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
+import {GridDataSource, GridColumn} from '@eg/share/grid/grid';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {LocaleService} from '@eg/core/locale.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.service';
+import {OrgFamily} from '@eg/share/org-family-select/org-family-select.component';
+
+@Component({
+    selector: 'eg-course-term-grid',
+    templateUrl: './course-term-grid.component.html'
+})
+
+export class TermListComponent implements OnInit, AfterViewInit {
+
+    @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+    @ViewChild('deleteLinkedTermWarning', { static: true }) deleteLinkedTermWarning: ConfirmDialogComponent;
+    @ViewChild('grid') grid: GridComponent;
+    @ViewChild('successString', { static: true }) successString: StringComponent;
+    @ViewChild('createString') createString: StringComponent;
+    @ViewChild('createErrString') createErrString: StringComponent;
+    @ViewChild('updateFailedString') updateFailedString: StringComponent;
+    @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent;
+    @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent;
+    @ViewChild('deleteLinkedTermWarningString', { static: true }) deleteLinkedTermWarningString: StringComponent;
+
+    @Input() sortField: string;
+    @Input() idlClass = 'acmt';
+    @Input() dialog_size: 'sm' | 'lg' = 'lg';
+    @Input() tableName = 'Term';
+    grid_source: GridDataSource = new GridDataSource();
+    currentMaterials: any[] = [];
+    search_value = '';
+    defaultOuId: number;
+    searchOrgs: OrgFamily;
+    defaultTerm: IdlObject;
+    termToDelete: String;
+
+
+    constructor(
+        private courseSvc: CourseService,
+        private locale: LocaleService,
+        private auth: AuthService,
+        private idl: IdlService,
+        private org: OrgService,
+        private pcrud: PcrudService,
+        private router: Router,
+        private toast: ToastService
+    ) {}
+
+    ngOnInit() {
+        this.getSource();
+        this.defaultTerm = this.idl.create('acmt');
+        this.defaultOuId = this.auth.user().ws_ou() || this.org.root().id();
+        this.defaultTerm.owning_lib(this.defaultOuId);
+        this.searchOrgs = {primaryOrgId: this.defaultOuId};
+    }
+
+    ngAfterViewInit() {
+        this.grid.onRowActivate.subscribe((term: IdlObject) => {
+            const idToEdit = term.id();
+            this.editSelected([term]);
+        });
+
+    }
+
+    /**
+     * Gets the data, specified by the class, that is available.
+     */
+    getSource() {
+        this.grid_source.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 search: any = new Array();
+            const orgFilter: any = {};
+            orgFilter['owning_lib'] =
+                this.searchOrgs.orgIds || [this.defaultOuId];
+            search.push(orgFilter);
+            const searchOps = {
+                offset: pager.offset,
+                limit: pager.limit,
+                order_by: orderBy
+            };
+            return this.pcrud.search(this.idlClass, search, searchOps, {fleshSelectors: true});
+        };
+    }
+
+    createNew() {
+        this.editDialog.mode = 'create';
+        const course_module_term = this.idl.create('acmt');
+        course_module_term.owning_lib(this.auth.user().ws_ou());
+        this.editDialog.recordId = null;
+        this.editDialog.record = course_module_term;
+        this.editDialog.open({size: this.dialog_size}).subscribe(
+            ok => {
+                this.createString.current()
+                    .then(str => this.toast.success(str));
+                this.grid.reload();
+            },
+            rejection => {
+                if (!rejection.dismissed) {
+                    this.createErrString.current()
+                        .then(str => this.toast.danger(str));
+                }
+            }
+        );
+    }
+
+    editSelected(fields: IdlObject[]) {
+        this.editDialog.mode = 'update';
+        // Edit each IDL thing one at a time
+        const course_ids = [];
+        fields.forEach(field => {
+            this.editDialog.record = field;
+            this.editDialog.open({size: this.dialog_size}).subscribe(
+                ok => {
+                    this.createString.current()
+                        .then(str => this.toast.success(str));
+                    this.grid.reload();
+                },
+                rejection => {
+                    if (!rejection.dismissed) {
+                        this.createErrString.current()
+                            .then(str => this.toast.danger(str));
+                    }
+                }
+            );
+        });
+    }
+
+    deleteSelected(fields: IdlObject[]) {
+        console.log(this.deleteLinkedTermWarningString);
+        fields.forEach(field => {
+            let termHasLinkedCourses = false;
+            this.courseSvc.getTermMaps(field.id()).subscribe(map => {
+                if (map) {
+                    termHasLinkedCourses = true;
+                }
+            }, err => {
+                console.error(err);
+            }, () => {
+                if (termHasLinkedCourses) {
+                    this.termToDelete = field.name();
+                    this.deleteLinkedTermWarning.open().toPromise().then(yes => {
+                        if (!yes) return;
+                        this.doDelete(field);
+                    });
+                } else {
+                    this.doDelete(field);
+                }
+            });
+        });
+
+    }
+
+    doDelete(idlThing: IdlObject) {
+        idlThing.isdeleted(true);
+        this.pcrud.autoApply(idlThing).subscribe(
+            val => {
+                console.debug('deleted: ' + val);
+                this.deleteSuccessString.current()
+                    .then(str => this.toast.success(str));
+            },
+            err => {
+                this.deleteFailedString.current()
+                    .then(str => this.toast.danger(str));
+            },
+            ()  => this.grid.reload()
+        );
+    }
+}
index 080626b..6c84647 100644 (file)
@@ -86,6 +86,19 @@ export class CourseService {
         });
     }
 
+    getTermMaps(term_ids) {
+        const flesher = {flesh: 2, flesh_fields: {
+            'acmtcm': ['course']}};
+
+        if (!term_ids) {
+            return this.pcrud.retrieveAll('acmtcm',
+                flesher);
+        } else {
+            return this.pcrud.search('acmtcm', {term: term_ids},
+                flesher);
+        }
+    }
+
     fetchCoursesForRecord(recordId) {
         const courseIds = new Set<number>();
         return this.pcrud.search(