LP1849212: Use a set list of roles for course users
authorJane Sandberg <sandbej@linnbenton.edu>
Wed, 2 Sep 2020 04:24:08 +0000 (21:24 -0700)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 22:17:36 +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>
12 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.html
Open-ILS/src/eg2/src/app/staff/share/course.service.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Courses.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Course.pm
Open-ILS/src/sql/Pg/040.schema.asset.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.course-materials-module.sql
Open-ILS/src/templates/opac/course/results.tt2
Open-ILS/src/templates/opac/parts/course/body.tt2

index d5e7377..3dc33be 100644 (file)
@@ -3143,12 +3143,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="ID" name="id" reporter:datatype="id" />
             <field reporter:label="Course" name="course" reporter:datatype="link" />
             <field reporter:label="User" name="usr" reporter:datatype="link" />
-            <field reporter:label="User Role" name="usr_role" reporter:datatype="text" />
-            <field reporter:label="OPAC Viewable?" name="is_public" reporter:datatype="bool" />
+            <field reporter:label="User Role" name="usr_role" reporter:datatype="link" />
         </fields>
         <links>
             <link field="course" reltype="has_a" key="id" map="" class="acmc" />
             <link field="usr" reltype="has_a" key="id" map="" class="au" />
+            <link field="usr_role" reltype="has_a" key="id" map="" class="acmr" />
         </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -3251,6 +3251,21 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
     </class>
+       <class id="acmr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_role" oils_persist:tablename="asset.course_module_role" reporter:label="Course Role" oils_persist:field_safe="true">
+               <fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_role_id_seq">
+                       <field reporter:label="Role 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="OPAC Viewable?" name="is_public" reporter:datatype="bool" />
+               </fields>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="MANAGE_RESERVES" global_required="true" />
+                               <retrieve/>
+                               <update permission="MANAGE_RESERVES" global_required="true" />
+                               <delete permission="MANAGE_RESERVES" global_required="true" />
+                       </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">
index f96087d..5959761 100644 (file)
             <div class="input-group-prepend">
               <label for="associate-user-role" class="input-group-text" i18n>Role</label>
             </div>
-            <input type="text" [(ngModel)]="userRoleInput" id="associate-user-role"
-              [disabled]="currentCourse && currentCourse.is_archived() == 't'"
-              placeholder-i18n placeholder="e.g. Student, TA, Instructor..."
-              class="flex-grow-1" />
+            <eg-combobox idlClass="acmr" [(ngModel)]="userRoleInput"
+            [disabled]="currentCourse && currentCourse.is_archived() == 't'">
+            </eg-combobox>
           </div>
         </div>
       </div>
       <div class="row mt-3">
-        <div [ngClass]="isDialog() ? 'offset-md-6 col-md-4' : 'col-md-6'">
-          <div class="input-group">
-            <div class="input-group-prepend">
-              <div class="input-group-text">
-                <label for="associate-user-public" i18n>Is Public Role?</label>
-              </div>
-            </div>
-            <div class="input-group-append">
-              <div class="input-group-text">
-                <input type="checkbox" [(ngModel)]="isPublicRole" id="associate-user-public"
-                  [disabled]="currentCourse && currentCourse.is_archived() == 't'"
-                  aria-label="Checkbox for allowing user to display on the OPAC Course Page" />
-              </div>
-            </div>
-          </div>
-        </div>
         <div class="text-right" [ngClass]="isDialog() ? 'col-md-2' : 'col-md-6'">
           <button class="btn btn-primary"
             [disabled]="currentCourse && currentCourse.is_archived() == 't'"
@@ -84,8 +67,8 @@
         <eg-grid-column label="Preferred Second Name" path="usr.pref_second_given_name"[hidden]="true"  i18n-label></eg-grid-column>
         <eg-grid-column label="Preferred Family Name" path="usr.pref_family_name"[hidden]="true"  i18n-label></eg-grid-column>
         <eg-grid-column label="Preferred Suffix" path="usr.pref_suffix" [hidden]="true" i18n-label></eg-grid-column>
-        <eg-grid-column label="User Role" path="usr_role" i18n-label></eg-grid-column>
-        <eg-grid-column label="Viewable on OPAC" path="is_public" i18n-label datatype="bool"></eg-grid-column>
+        <eg-grid-column label="User Role" path="usr_role.name" i18n-label></eg-grid-column>
+        <eg-grid-column label="Viewable on OPAC" path="usr_role.is_public" i18n-label datatype="bool"></eg-grid-column>
       </eg-grid>
     </div>
   </div>
index d9360cc..55d77b4 100644 (file)
@@ -7,11 +7,12 @@ import {Pager} from '@eg/share/util/pager';
 import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
 import {GridDataSource} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
-import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {IdlObject} from '@eg/core/idl.service';
 import {StringComponent} from '@eg/share/string/string.component';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {CourseService} from '@eg/staff/share/course.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 
 @Component({
     selector: 'eg-course-associate-users-dialog',
@@ -39,8 +40,7 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
         userEditFailedString: StringComponent;
     usersDataSource: GridDataSource;
     userBarcode: String;
-    userRoleInput: String;
-    isPublicRole: Boolean;
+    userRoleInput: ComboboxEntry;
 
     constructor(
         private auth: AuthService,
@@ -69,10 +69,12 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
             const args = {
                 currentCourse: this.currentCourse,
                 barcode: barcode.trim(),
-                role: this.userRoleInput,
-                is_public: this.isPublicRole
             };
 
+            if (this.userRoleInput) {
+                args['role'] = this.userRoleInput.id;
+            }
+
             this.userBarcode = null;
 
             this.net.request(
index 13f9932..3fac600 100644 (file)
@@ -33,7 +33,7 @@
           <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>      
+      </div>
     </ng-template>
   </li>
   <li ngbNavItem>
       <eg-admin-page idlClass="acmt"></eg-admin-page>
     </ng-template>
   </li>
+  <li ngbNavItem>
+    <a ngbNavLink i18n>Course roles</a>
+    <ng-template ngbNavContent>
+      <eg-admin-page idlClass="acmr"></eg-admin-page>
+    </ng-template>
+  </li>
 </ul>
 <div [ngbNavOutlet]="courseListNav"></div>
 
index c3e480d..43caf76 100644 (file)
@@ -53,7 +53,7 @@ export class CourseService {
     getUsers(course_ids?: Number[]): Observable<IdlObject> {
         const flesher = {
             flesh: 1,
-            flesh_fields: {'acmcu': ['usr']}
+            flesh_fields: {'acmcu': ['usr', 'usr_role']}
         };
         if (!course_ids) {
             return this.pcrud.retrieveAll('acmcu',
@@ -134,7 +134,6 @@ export class CourseService {
 
     associateUsers(patron_id, args) {
         const new_user = this.idl.create('acmcu');
-        if (args.is_public) { new_user.is_public(args.is_public); }
         if (args.role) { new_user.usr_role(args.role); }
         new_user.course(args.currentCourse.id());
         new_user.usr(patron_id);
index 836a934..a68ce6b 100644 (file)
@@ -191,11 +191,11 @@ sub fetch_course_users {
     my %patrons;
 
     $filter->{course} = $course_id;
-    $filter->{is_public} = 't'
+    $filter->{usr_role}->{is_public} = 't'
         unless ($self->api_name =~ /\.staff/) and $e->allowed('MANAGE_RESERVES');
  
  
-    $users->{list} =  $e->search_asset_course_module_course_users($filter, {order_by => {acmcu => 'id'}});
+    $users->{list} =  $e->search_asset_course_module_course_users($filter, {flesh => 1, flesh_fields => {acmcu => ['usr_role']}, order_by => {acmcu => 'id'}});
     for my $course_user (@{$users->{list}}) {
         my $patron = {};
         $patron->{id} = $course_user->id;
index eaffd44..88c35aa 100644 (file)
@@ -64,10 +64,9 @@ sub load_course_browse {
                 "select" => {"acmcu" => [
                     'id',
                     'usr',
-                    'is_public'
                 ]},
                 # TODO: We need to support the chosen library as well...
-                "where" => {'+acmcu' => 'is_public'}
+                "where" => {'usr_role' => {'in' => {'select' => {'acmr' => ['id']}, 'where' => {'+acmr' => 'is_public'}}}}
             });
             $results = $e->json_query({
                 "from" => "au",
@@ -88,7 +87,12 @@ sub load_course_browse {
                             "acmcu" => ['usr']
                         },
                         "where" => {'-and' => [
-                            {'+acmcu' => 'is_public'},
+                            {'usr_role' => { 'in' => {
+                                'from' => 'acmr',
+                                "select" => {
+                                    "acmr" => ['id']
+                                },
+                                "where" => {'+acmr' => 'is_public'}}}},
                             {"course" => { "in" =>{
                                 "from" => "acmc",
                                 "select" => {
index 04f6b90..426b460 100644 (file)
@@ -1114,12 +1114,17 @@ CREATE TABLE asset.course_module_course (
     is_archived        BOOLEAN DEFAULT false
 );
 
+CREATE TABLE asset.course_module_role (
+    id              SERIAL  PRIMARY KEY,
+    name            TEXT    UNIQUE NOT NULL,
+    is_public       BOOLEAN NOT NULL DEFAULT false
+);
+
 CREATE TABLE asset.course_module_course_users (
     id              SERIAL PRIMARY KEY,
     course          INT NOT NULL REFERENCES asset.course_module_course (id),
     usr             INT NOT NULL REFERENCES actor.usr (id),
-    usr_role        TEXT,
-    is_public       BOOLEAN NOT NULL DEFAULT false
+    usr_role        INT REFERENCES asset.course_module_role (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
 );
 
 CREATE TABLE asset.course_module_course_materials (
index b62ea32..d5b0946 100644 (file)
@@ -20639,6 +20639,11 @@ INSERT INTO actor.org_unit_setting (org_unit, name, value)
     FROM config.bib_source
     WHERE source='Course materials module';
 
+INSERT INTO asset.course_module_role (id, name, is_public) VALUES
+(1, oils_i18n_gettext(1, 'Instructor', 'acmr', 'name'), true),
+(2, oils_i18n_gettext(2, 'Teaching assistant', 'acmr', 'name'), true),
+(3, oils_i18n_gettext(2, 'Student', 'acmr', 'name'), false);
+
 
 INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
 VALUES (
index 26dc6c7..7d903a4 100644 (file)
@@ -11,12 +11,17 @@ CREATE TABLE asset.course_module_course (
     is_archived        BOOLEAN NOT NULL DEFAULT false;
 );
 
+CREATE TABLE asset.course_module_role (
+    id              SERIAL  PRIMARY KEY,
+    name            TEXT    UNIQUE NOT NULL,
+    is_public       BOOLEAN NOT NULL DEFAULT false
+);
+
 CREATE TABLE asset.course_module_course_users (
     id              SERIAL PRIMARY KEY,
     course          INT NOT NULL REFERENCES asset.course_module_course (id),
     usr             INT NOT NULL REFERENCES actor.usr (id),
-    usr_role        TEXT,
-    is_public       BOOLEAN NOT NULL DEFAULT false
+    usr_role        INT REFERENCES asset.course_module_role (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
 );
 
 CREATE TABLE asset.course_module_course_materials (
@@ -25,11 +30,11 @@ CREATE TABLE asset.course_module_course_materials (
     item            INT REFERENCES asset.copy (id),
     relationship    TEXT,
     record          INT REFERENCES biblio.record_entry (id),
-    temporary_record         BOOLEAN,
-    original_location        INT REFERENCES asset.copy_location,
-    original_status          INT REFERENCES config.copy_status,
-    original_circ_modifier   TEXT, --REFERENCES config.circ_modifier,
-    original_callnumber      INT REFERENCES asset.call_number,
+    temporary_record       BOOLEAN,
+    original_location      INT REFERENCES asset.copy_location,
+    original_status        INT REFERENCES config.copy_status,
+    original_circ_modifier TEXT, --REFERENCES config.circ_modifier
+    original_callnumber    INT REFERENCES asset.call_number,
     unique (course, item, record)
 );
 
@@ -37,10 +42,15 @@ 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
+    start_date      TIMESTAMP WITH TIME ZONE,
+    end_date        TIMESTAMP WITH TIME ZONE
 );
 
+INSERT INTO asset.course_module_role (id, name, is_public) VALUES
+(1, oils_i18n_gettext(1, 'Instructor', 'acmr', 'name'), true),
+(2, oils_i18n_gettext(2, 'Teaching assistant', 'acmr', 'name'), true),
+(3, oils_i18n_gettext(2, 'Student', 'acmr', 'name'), false);
+
 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,
index f8ae3b9..d2d68ca 100644 (file)
@@ -86,7 +86,7 @@
                     href="[%
                        mkurl(ctx.opac_root _ '/results', {qtype => 'instructor', query => instructorString})
                     %]" rel="nofollow" vocab="">
-                  [% instructorString %] ([% l(instructor.usr_role) %])</a>. 
+                  [% instructorString %] ([% l(instructor.usr_role.name) %])</a>.
                   [% END %]
                 </div>
                 <div>
index 2d80caf..7f6333a 100644 (file)
@@ -24,7 +24,7 @@
           ELSE;
             instructorString = instructorString _ instructor.first_given_name;
           END;
-          instructorString = instructorString _ ' (' _ l(instructor.usr_role) _ ')'; %]
+          instructorString = instructorString _ ' (' _ l(instructor.usr_role.name) _ ')'; %]
           <span class="course-instructor-div">[% instructorString %].</span>
         [% END %]
       </div>