LP 1849212: Add course term functionality
authorJane Sandberg <sandbej@linnbenton.edu>
Sat, 15 Aug 2020 15:21:46 +0000 (08:21 -0700)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 22:17:24 +0000 (18:17 -0400)
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

Open-ILS/examples/fm_IDL.xml
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-list.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-page.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-page.component.ts
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-map.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.course-materials-module.sql

index 87288f5..d5e7377 100644 (file)
@@ -3121,11 +3121,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Course Members" name="members" oils_persist:virtual="true" reporter:datatype="link" />
             <field reporter:label="Course Materials" name="materials" oils_persist:virtual="true" reporter:datatype="link" />
             <field reporter:label="Is Archived?" name="is_archived" reporter:datatype="bool" />
+                       <field reporter:label="Terms Taught" name="terms_map" oils_persist:virtual="true" reporter:datatype="link" config_field="true" />
         </fields>
         <links>
             <link field="owning_lib" reltype="has_a" key="id" map="" class="aou" />
             <link field="members" reltype="has_many" key="course" map="" class="acmcu" />
             <link field="materials" reltype="has_many" key="course" map="" class="acmcm" />
+                       <link field="terms_map" reltype="has_many" key="course" map="" class="acmtcm" />
         </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -3200,6 +3202,56 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
     </class>
+       <class id="acmt" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_term" oils_persist:tablename="asset.course_module_term" reporter:label="Term" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_term_id_seq">
+                       <field reporter:label="Term ID" name="id" reporter:datatype="id" reporter:selector="name"/>
+                       <field reporter:label="Name" name="name" reporter:datatype="text" oils_persist:i18n="true" oils_obj:required="true"/>
+            <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="link" oils_obj:required="true" />
+                       <field reporter:label="Start Date" name="start_date" reporter:datatype="timestamp"/>
+                       <field reporter:label="End Date" name="end_date" reporter:datatype="timestamp"/>
+                       <field reporter:label="Courses" name="courses" reporter:datatype="link" oils_persist:virtual="true"/>
+                       <field reporter:label="Course Maps" name="maps" reporter:datatype="link" oils_persist:virtual="true"/>
+               </fields>
+               <links>
+            <link field="owning_lib" reltype="has_a" key="id" map="" class="aou" />
+                       <link field="courses" reltype="has_many" key="term" map="course" class="acmtcm"/>
+                       <link field="maps" reltype="has_many" key="term" map="" class="acmtcm"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="MANAGE_RESERVES" context_field="owning_lib" />
+                               <retrieve/>
+                               <update permission="MANAGE_RESERVES" context_field="owning_lib" />
+                               <delete permission="MANAGE_RESERVES" context_field="owning_lib" />
+                       </actions>
+               </permacrud>
+       </class>
+    <class id="acmtcm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_term_course_map" oils_persist:tablename="asset.course_module_term_course_map" reporter:label="Course Term Map" oils_persist:field_safe="true">
+        <fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_term_course_map_id_seq">
+            <field reporter:label="Course Term Map ID" name="id" reporter:datatype="id"/>
+            <field reporter:label="Term" name="term" oils_obj:required="true" reporter:datatype="link"/>
+            <field reporter:label="Course" name="course" reporter:datatype="link" oils_obj:required="true"/>
+        </fields>
+        <links>
+            <link field="term" reltype="has_a" key="id" map="" class="acmt"/>
+            <link field="course" reltype="has_a" key="id" map="" class="acmc"/>
+        </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create permission="MANAGE_RESERVES">
+                                       <context link="course" field="owning_lib" />
+                </create>
+                <retrieve/>
+                <update permission="MANAGE_RESERVES">
+                                       <context link="course" field="owning_lib" />
+                </update>
+                <delete permission="MANAGE_RESERVES">
+                                       <context link="course" field="owning_lib" />
+                </delete>
+            </actions>
+        </permacrud>
+    </class>
+       
     <class id="acnc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::call_number_class" oils_persist:tablename="asset.call_number_class" reporter:label="Call number classification scheme">
         <fields oils_persist:primary="id" oils_persist:sequence="asset.call_number_class_id_seq">
             <field reporter:label="Call number class ID" name="id" reporter:datatype="id"/>
index 54d0c7d..e8e470a 100644 (file)
@@ -9,26 +9,41 @@
 <eg-string #archiveSuccessString i18n-text text="Archival of {{tableName}} succeeded"></eg-string>
 <eg-string #flairTooltip i18n-text text="Limited Editing"></eg-string>
 
-<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-toolbar-action label="Archive Selected" i18n-label (onClick)="archiveSelected($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="Title" name="name" i18n-label></eg-grid-column>
-    <eg-grid-column label="CourseNumber" name="course_number" i18n-label></eg-grid-column>
-    <eg-grid-column label="Section Number" name="section_number" i18n-label></eg-grid-column>
-    <eg-grid-column label="Is Archived?" name="is_archived" i18n-label datatype="bool"></eg-grid-column>
-  </eg-grid>
-</div>
+<ul ngbNav #courseListNav="ngbNav" class="nav-tabs">
+  <li ngbNavItem>
+    <a ngbNavLink i18n>Course list</a>
+    <ng-template ngbNavContent>
+      <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-toolbar-action label="Archive Selected" i18n-label (onClick)="archiveSelected($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="Terms taught" name="terms_map" i18n-label [cellTemplate]="termMapLink"></eg-grid-column>
+          <eg-grid-column label="Title" name="name" i18n-label></eg-grid-column>
+          <eg-grid-column label="CourseNumber" name="course_number" i18n-label></eg-grid-column>
+          <eg-grid-column label="Section Number" name="section_number" i18n-label></eg-grid-column>
+          <eg-grid-column label="Is Archived?" name="is_archived" i18n-label datatype="bool"></eg-grid-column>
+        </eg-grid>
+      </div>      
+    </ng-template>
+  </li>
+  <li ngbNavItem>
+    <a ngbNavLink i18n>Terms</a>
+    <ng-template ngbNavContent>
+      <eg-admin-page idlClass="acmt"></eg-admin-page>
+    </ng-template>
+  </li>
+</ul>
+<div [ngbNavOutlet]="courseListNav"></div>
 
 <eg-fm-record-editor #editDialog
   idlClass="acmc"
@@ -36,3 +51,9 @@
   [preloadLinkedValues]="true"
   hiddenFields="id,is_archived">
 </eg-fm-record-editor>
+
+<ng-template #termMapLink let-row="row">
+  <a routerLink="/staff/admin/local/asset/course_module_term_course_map" [queryParams]="acmtcmQueryParams(row)" i18n>
+    Terms taught
+  </a>
+</ng-template>
index bc439a4..8459935 100644 (file)
@@ -46,6 +46,7 @@ export class CourseListComponent implements OnInit {
     currentMaterials: any[] = [];
     search_value = '';
 
+
     constructor(
         private courseSvc: CourseService,
         private locale: LocaleService,
@@ -60,8 +61,14 @@ export class CourseListComponent implements OnInit {
             const idToEdit = course.id();
             this.navigateToCoursePage(idToEdit);
         });
+
     }
 
+    acmtcmQueryParams (row: any): {gridFilters: string} {
+        return {gridFilters: '{"course":' + row.id() + '}'};
+    }
+
+
     /**
      * Gets the data, specified by the class, that is available.
      */
index 39397d5..9d57bc0 100644 (file)
       </eg-course-associate-users-dialog>
     </ng-template>
   </li>
+
+  <!-- Terms Tab -->
+  <li [ngbNavItem]="'courseTerms'">
+    <a ngbNavLink i18n>Course terms</a>
+    <ng-template ngbNavContent>
+      <eg-admin-page idlClass="acmtcm" readonlyFields="id"
+        [defaultNewRecord]="defaultNewAcmtcm"
+        hideGridFields="id" [dataSource]="termsDataSource">
+      </eg-admin-page>
+    </ng-template>
+  </li>
 </ul>
 <div [ngbNavOutlet]="coursePageNav" class="mb-3"></div>
 
index df7a41a..07d5cbf 100644 (file)
@@ -1,12 +1,14 @@
-import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
+import {Component, ViewChild, OnInit} from '@angular/core';
 import {ActivatedRoute} from '@angular/router';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {GridDataSource} from '@eg/share/grid/grid';
 import {StringComponent} from '@eg/share/string/string.component';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {CourseService} from '@eg/staff/share/course.service';
 import {CourseAssociateUsersComponent} from './course-associate-users.component';
 import {CourseAssociateMaterialComponent} from './course-associate-material.component';
+import {Pager} from '@eg/share/util/pager';
 
 @Component({
     selector: 'eg-course-page',
@@ -18,6 +20,7 @@ export class CoursePageComponent implements OnInit {
     currentCourse: IdlObject;
     courseId: any;
 
+    // Materials Tab
     @ViewChild('courseMaterialDialog', {static: true})
         private courseMaterialDialog: CourseAssociateMaterialComponent;
     @ViewChild('courseUserDialog', {static: true})
@@ -29,10 +32,13 @@ export class CoursePageComponent implements OnInit {
     @ViewChild('archiveSuccessString', { static: true })
         archiveSuccessString: StringComponent;
 
-    // Materials Tab
+    // Course Tab
+    termsDataSource: GridDataSource = new GridDataSource();
+    defaultNewAcmtcm: IdlObject;
 
     constructor(
         private course: CourseService,
+        private idl: IdlService,
         private pcrud: PcrudService,
         private route: ActivatedRoute,
         private toast: ToastService
@@ -44,6 +50,24 @@ export class CoursePageComponent implements OnInit {
         this.course.getCourses([this.courseId]).then(course => {
             this.currentCourse = course[0];
         });
+
+        this.defaultNewAcmtcm = this.idl.create('acmtcm');
+        this.defaultNewAcmtcm.course(this.courseId);
+
+        this.termsDataSource.getRows = (pager: Pager, sort: any[]) => {
+            const orderBy: any = {};
+            if (sort.length) {
+                orderBy.acmtcm = sort[0].name + ' ' + sort[0].dir;
+            }
+            const searchOps = {
+                offset: pager.offset,
+                limit: pager.limit,
+                order_by: orderBy
+            };
+
+            return this.pcrud.search('acmtcm', {course: this.courseId},
+                searchOps, {fleshSelectors: true});
+        };
     }
 
     // Edit Tab
index 96802cf..30c7651 100644 (file)
@@ -9,6 +9,7 @@ import {CourseAssociateUsersComponent} from './course-associate-users.component'
 import {CourseReservesRoutingModule} from './routing.module';
 import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
 import {MarcSimplifiedEditorModule} from '@eg/staff/share/marc-edit/simplified-editor/simplified-editor.module';
+import {CourseTermMapComponent} from './course-term-map.component';
 
 @NgModule({
   declarations: [
@@ -16,6 +17,7 @@ import {MarcSimplifiedEditorModule} from '@eg/staff/share/marc-edit/simplified-e
     CoursePageComponent,
     CourseAssociateMaterialComponent,
     CourseAssociateUsersComponent,
+    CourseTermMapComponent
   ],
   imports: [
     StaffCommonModule,
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-map.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-term-map.component.ts
new file mode 100644 (file)
index 0000000..7fb460d
--- /dev/null
@@ -0,0 +1,28 @@
+import {Component} from '@angular/core';
+
+/**
+ * Very basic page for editing course/term map
+ */
+
+@Component({
+    template: `
+        <eg-title i18n-prefix prefix="Course Materials Administration">
+        </eg-title>
+        <eg-staff-banner bannerText="Course Term Configuration" i18n-bannerText>
+        </eg-staff-banner>
+        <div class="row">
+            <div class="col text-right">
+                <a class="btn btn-warning ml-3" routerLink="/staff/admin/local/asset/course_list" i18n>
+                    <i class="material-icons align-middle">keyboard_return</i>
+                    <span class="align-middle">Return to Course List</span>
+                </a>
+            </div>
+        </div>
+        <eg-admin-page persistKeyPfx="local" idlClass="acmtcm"
+            [disableOrgFilter]="true"></eg-admin-page>
+    `
+})
+
+export class CourseTermMapComponent {
+
+}
\ No newline at end of file
index e0e5414..69ba254 100644 (file)
@@ -5,6 +5,7 @@ import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.componen
 import {AddressAlertComponent} from './address-alert.component';
 import {AdminCarouselComponent} from './admin-carousel.component';
 import {StandingPenaltyComponent} from './standing-penalty.component';
+import {CourseTermMapComponent} from './course-reserves/course-term-map.component';
 
 const routes: Routes = [{
     path: 'splash',
@@ -23,6 +24,9 @@ const routes: Routes = [{
     path: 'asset/course_list',
     loadChildren: '@eg/staff/admin/local/course-reserves/course-reserves.module#CourseReservesModule'
 }, {
+    path: 'asset/course_module_term_course_map',
+    component: CourseTermMapComponent
+}, {
     path: 'config/standing_penalty',
     component: StandingPenaltyComponent
 }, {
index 0207797..04f6b90 100644 (file)
@@ -1136,5 +1136,19 @@ CREATE TABLE asset.course_module_course_materials (
     unique (course, item, record)
 );
 
+CREATE TABLE asset.course_module_term (
+    id              SERIAL  PRIMARY KEY,
+    name            TEXT    UNIQUE NOT NULL,
+    owning_lib      INT REFERENCES actor.org_unit (id),
+       start_date      TIMESTAMP WITH TIME ZONE,
+       end_date        TIMESTAMP WITH TIME ZONE
+);
+
+CREATE TABLE asset.course_module_term_course_map (
+    id              BIGSERIAL  PRIMARY KEY,
+    term            INT     NOT NULL REFERENCES asset.course_module_term (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+    course          INT     NOT NULL REFERENCES asset.course_module_course (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
+);
+
 COMMIT;
 
index a1fc120..26dc6c7 100644 (file)
@@ -33,6 +33,20 @@ CREATE TABLE asset.course_module_course_materials (
     unique (course, item, record)
 );
 
+CREATE TABLE asset.course_module_term (
+    id              SERIAL  PRIMARY KEY,
+    name            TEXT    UNIQUE NOT NULL,
+    owning_lib      INT REFERENCES actor.org_unit (id),
+       start_date      TIMESTAMP WITH TIME ZONE,
+       end_date        TIMESTAMP WITH TIME ZONE
+);
+
+CREATE TABLE asset.course_module_term_course_map (
+    id              BIGSERIAL  PRIMARY KEY,
+    term            INT     NOT NULL REFERENCES asset.course_module_term (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+    course          INT     NOT NULL REFERENCES asset.course_module_course (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
+);
+
 INSERT INTO permission.perm_list(id, code, description)
     VALUES (
         624,