<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="non_cat_materials" reltype="has_many" key="course" map="" class="acmncm" />
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
<field reporter:label="Course" name="course" reporter:datatype="link" />
<field reporter:label="Item" name="item" reporter:datatype="link" />
<field reporter:label="Record" name="record" reporter:datatype="link" />
+ <field reporter:label="Record is temporary?" name="temporary_record" reporter:datatype="bool" />
<field reporter:label="Item Relationship" name="relationship" reporter:datatype="text" />
<field reporter:label="Original Status" name="original_status" reporter:datatype="link" />
<field reporter:label="Original Circ Modifier" name="original_circ_modifier" reporter:datatype="link" />
</actions>
</permacrud>
</class>
- <class id="acmncm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_non_cat_course_materials" oils_persist:tablename="asset.course_module_non_cat_course_materials" reporter:label="Non-Cataloged Course Materials">
- <fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_non_cat_course_materials_id_seq">
- <field reporter:label="ID" name="id" reporter:datatype="id" />
- <field reporter:label="Course" name="course" reporter:datatype="link" />
- <field reporter:label="Title" name="title" reporter:datatype="text" />
- <field reporter:label="URL" name="url" reporter:datatype="text" />
- <field reporter:label="Item Relationship" name="relationship" reporter:datatype="text" />
- </fields>
- <links>
- <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"/>
<request_timeout>60</request_timeout>
</app_settings>
</open-ils.ebook_api>
+
+ <open-ils.courses>
+ <keepalive>5</keepalive>
+ <stateless>1</stateless>
+ <language>perl</language>
+ <implementation>OpenILS::Application::Courses</implementation>
+ <max_requests>100</max_requests>
+ <unix_config>
+ <unix_sock>courses_unix.sock</unix_sock>
+ <unix_pid>courses_unix.pid</unix_pid>
+ <unix_log>courses_unix.log</unix_log>
+ <max_requests>100</max_requests>
+ <min_children>1</min_children>
+ <max_children>15</max_children>
+ <min_spare_children>1</min_spare_children>
+ <max_spare_children>5</max_spare_children>
+ </unix_config>
+ <app_settings>
+ </app_settings>
+ </open-ils.courses>
</apps>
</default>
<appname>open-ils.serial</appname>
<appname>open-ils.hold-targeter</appname>
<appname>open-ils.ebook_api</appname>
+ <appname>open-ils.courses</appname>
</activeapps>
</localhost>
</hosts>
<service>open-ils.cat</service>
<service>open-ils.circ</service>
<service>open-ils.collections</service>
+ <service>open-ils.courses</service>
<service>open-ils.fielder</service>
<service>open-ils.pcrud</service>
<service>open-ils.permacrud</service>
-
-<eg-string #materialDeleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed"></eg-string>
+<eg-string #materialDeleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed">
+</eg-string>
<eg-string #materialDeleteSuccessString i18n-text text="Disassociation of Course Material succeeded"></eg-string>
<eg-string #materialAddSuccessString i18n-text text="Association of Course Material succeeded"></eg-string>
-<eg-string #materialAddFailedString i18n-text text="Association of Course Material failed or was not allowed"></eg-string>
+<eg-string #materialAddFailedString i18n-text text="Association of Course Material failed or was not allowed">
+</eg-string>
<eg-string #materialEditSuccessString i18n-text text="Update of Course Material succeeded"></eg-string>
<eg-string #materialEditFailedString i18n-text text="Update of Course Material failed or was not allowed"></eg-string>
<eg-string #MaterialAddDifferentLibraryString i18n-text text="Material exists at a different library"></eg-string>
<ng-template #dialogContent>
-<div class="modal-header bg-info"
- [ngClass]="isDialog() ? 'modal-header' : 'alert mt-3'">
- <h4 class="modal-title" i18n>Course Materials</h4>
- <ng-container *ngIf="isDialog()">
- <button type="button" class="close"
- i18n-aria-label aria-label="Close" (click)="close()">
- <span aria-hidden="true">×</span>
- </button>
- </ng-container>
-</div>
-<div [ngClass]="isDialog() ? 'modal-body' : ''">
- <div class="row">
- <div [ngClass]="isDialog() ? 'col-md-12' : 'col-md-4'">
- <div class="row" [ngClass]="isDialog() ? '' : 'mt-3'">
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Barcode</span>
- </div>
- <input type="text" class="flex-grow-1" [(ngModel)]="barcodeInput"
- (click)="$event.target.select()"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
- </div>
- </div>
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Relationship</span>
+ <div class="modal-header bg-info" [ngClass]="isDialog() ? 'modal-header' : 'alert mt-3'">
+ <h4 class="modal-title" i18n>Course Materials</h4>
+ <ng-container *ngIf="isDialog()">
+ <button type="button" class="close" i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </ng-container>
+ </div>
+ <div [ngClass]="isDialog() ? 'modal-body' : ''">
+ <div class="row">
+ <ngb-tabset [ngClass]="isDialog() ? 'col-md-12' : 'col-md-4'" type="tabs">
+ <ngb-tab title="Associate item" i18n-title>
+ <ng-template ngbTabContent>
+ <div class="row" [ngClass]="isDialog() ? '' : 'mt-3'">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Barcode</span>
+ </div>
+ <input type="text" class="flex-grow-1" [(ngModel)]="barcodeInput" (click)="$event.target.select()"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
+ </div>
+ </div>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Relationship</span>
+ </div>
+ <input type="text" [(ngModel)]="relationshipInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" placeholder-i18n
+ placeholder="e.g. Required" class="flex-grow-1" />
+ </div>
+ </div>
</div>
- <input type="text" [(ngModel)]="relationshipInput"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- placeholder-i18n placeholder="e.g. Required"
- class="flex-grow-1" />
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 text-right">
- <button class="btn btn-primary"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (click)="associateItem(barcodeInput, relationshipInput)"
- i18n [disabled]="!barcodeInput">
- Add Material
- </button>
- </div>
- </div>
- <div class="row justify-content-center mt-3">
- <div class="col">
- <h5 i18n>The following fields will be applied to the material
- added, and reverted once the course is no longer associated
- with the material.</h5>
- </div>
- </div>
- <div class="row mt-3">
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Call Number</span>
+ <div class="row mt-3">
+ <div class="col-lg-12 text-right">
+ <button class="btn btn-primary" [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (click)="associateItem(barcodeInput, relationshipInput)" i18n [disabled]="!barcodeInput">
+ Add Material
+ </button>
</div>
</div>
- <input type="text" [(ngModel)]="tempCallNumber"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (input)="isModifyingCallNumber = true" class="flex-grow-1" />
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Call Number" />
+ <div class="row justify-content-center mt-3">
+ <div class="col">
+ <h5 i18n>The following fields will be applied to the material
+ added, and reverted once the course is no longer associated
+ with the material.</h5>
</div>
</div>
- </div>
- </div>
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Circulation Modifier</span>
+ <div class="row mt-3">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Call Number</span>
+ </div>
+ </div>
+ <input type="text" [(ngModel)]="tempCallNumber"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (input)="isModifyingCallNumber = true" class="flex-grow-1" />
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Call Number" />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Circulation Modifier</span>
+ </div>
+ </div>
+ <eg-combobox i18n-placeholder placeholder="Circulation Modifier..." idlClass="ccm" idlField="name"
+ [displayTemplate]="idlClassLabel" [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
+ (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
+ </eg-combobox>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingCircMod"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Circulation Modifier" />
+ </div>
+ </div>
+ </div>
</div>
</div>
- <eg-combobox i18n-placeholder placeholder="Circulation Modifier..."
- idlClass="ccm" idlField="name" [displayTemplate]="idlClassLabel"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
- (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCircMod"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Circulation Modifier" />
+ <div class="row mt-3">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Item Status</span>
+ </div>
+ </div>
+ <eg-combobox i18n-placeholder placeholder="Item Status..." idlClass="ccs" idlField="name"
+ [displayTemplate]="idlClassLabel" [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
+ (onChange)="tempStatus = $event.id; isModifyingStatus = true">
+ </eg-combobox>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingStatus"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Item Status" />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Shelving Location</span>
+ </div>
+ </div>
+ <eg-item-location-select permFilter="MANAGE_RESERVES" class="flex-grow-1"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" [(ngModel)]="tempLocation"
+ (valueChange)="isModifyingLocation = true">
+ </eg-item-location-select>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingLocation"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Shelving Location" />
+ </div>
+ </div>
+ </div>
</div>
</div>
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Item Status</span>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Associate brief record" i18n-title>
+ <ng-template ngbTabContent>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Relationship</span>
+ </div>
+ <input type="text" [(ngModel)]="relationshipInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" placeholder-i18n
+ placeholder="e.g. Required" class="flex-grow-1" />
</div>
</div>
- <eg-combobox i18n-placeholder placeholder="Item Status..."
- idlClass="ccs" idlField="name" [displayTemplate]="idlClassLabel"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
- (onChange)="tempStatus = $event.id; isModifyingStatus = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingStatus"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Item Status" />
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Relationship</span>
+ </div>
+ <input type="text" [(ngModel)]="relationshipInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" placeholder-i18n
+ placeholder="e.g. Required" class="flex-grow-1" />
</div>
</div>
- </div>
- </div>
- <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Shelving Location</span>
+ <eg-marc-simplified-editor (xmlRecordEvent)="associateBriefRecord($event)" buttonLabel="Add material" i18n-buttonLabel>
+ <eg-marc-simplified-editor-field tag="245" subfield="a"></eg-marc-simplified-editor-field>
+ <eg-marc-simplified-editor-field tag="856" subfield="u"></eg-marc-simplified-editor-field>
+ <eg-marc-simplified-editor-field tag="856" subfield="9" defaultValue="CONS"></eg-marc-simplified-editor-field>
+ <eg-marc-simplified-editor-field tag="990" subfield="a" i18n-defaultValue
+ defaultValue="This record was created using the Course Materials Module -- please edit it there">
+ </eg-marc-simplified-editor-field>
+ </eg-marc-simplified-editor>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="Associate electronic resource from catalog" i18n-title>
+ <ng-template ngbTabContent>
+ <div class="row" [ngClass]="isDialog() ? '' : 'mt-3'">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Relationship</span>
+ </div>
+ <input type="text" [(ngModel)]="relationshipInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" class="flex-grow-1" />
+ </div>
+ </div>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <label for="bib-id" class="input-group-text" i18n>Bibliographic Record ID</label>
+ </div>
+ <input type="text" [(ngModel)]="bibId" id="bib-id"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" class="flex-grow-1" />
+ </div>
</div>
</div>
- <eg-item-location-select permFilter="MANAGE_RESERVES" class="flex-grow-1"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [(ngModel)]="tempLocation" (valueChange)="isModifyingLocation = true">
- </eg-item-location-select>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingLocation"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Shelving Location" />
+ <div class="row mt-3">
+ <div class="col-lg-12 text-right">
+ <button class="btn btn-primary" i18n (click)="associateElectronicBibRecord()"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'">
+ Add Material
+ </button>
</div>
</div>
- </div>
- </div>
- </div>
- </div>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
- <div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
- <eg-grid #materialsGrid [dataSource]="materialsDataSource">
- <eg-grid-toolbar-action label="Remove Selected" i18n-label (onClick)="deleteSelectedMaterials($event)">
- </eg-grid-toolbar-action>
- <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelectedMaterials($event)">
- </eg-grid-toolbar-action>
- <eg-grid-column path="_id" [index]=true [hidden]="true" label="ID" i18n-label></eg-grid-column>
- <eg-grid-column label="Barcode" i18n-label name="card" [cellTemplate]="barcodeCellTemplate"></eg-grid-column>
- <eg-grid-column label="Title" i18n-label name="title" [cellTemplate]="titleCellTemplate"></eg-grid-column>
- <eg-grid-column path="call_number.label" label="Call Number" i18n-label></eg-grid-column>
- <eg-grid-column path="call_number.prefix.label" [hidden]="true" label="Call Number Prefix" i18n-label hidden></eg-grid-column>
- <eg-grid-column path="call_number.suffix.label" [hidden]="true" label="Call Number Suffix" i18n-label hidden></eg-grid-column>
- <eg-grid-column path="circ_modifier" [hidden]="true" label="Circulation Modifier" i18n-label></eg-grid-column>
- <eg-grid-column path="circ_lib.shortname" label="Circulation Library" i18n-label></eg-grid-column>
- <eg-grid-column path="location.name" [hidden]="true" label="Shelving Location" i18n-label></eg-grid-column>
- <eg-grid-column path="status.name" [hidden]="true" label="Copy Status" i18n-label></eg-grid-column>
- <eg-grid-column path="_relationship" label="Relationship" i18n-label></eg-grid-column>
- </eg-grid>
+ <div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
+ <eg-grid #materialsGrid [dataSource]="materialsDataSource">
+ <eg-grid-toolbar-action label="Remove Selected" i18n-label (onClick)="deleteSelectedMaterials($event)">
+ </eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelectedMaterials($event)">
+ </eg-grid-toolbar-action>
+ <eg-grid-column path="id" [index]=true [hidden]="true" label="ID" i18n-label></eg-grid-column>
+ <eg-grid-column label="Barcode" i18n-label name="card" [cellTemplate]="barcodeCellTemplate"></eg-grid-column>
+ <eg-grid-column label="Title" i18n-label name="title" [cellTemplate]="titleCellTemplate"></eg-grid-column>
+ <eg-grid-column path="call_number.label" label="Call Number" i18n-label></eg-grid-column>
+ <eg-grid-column path="call_number.prefix.label" [hidden]="true" label="Call Number Prefix" i18n-label hidden>
+ </eg-grid-column>
+ <eg-grid-column path="call_number.suffix.label" [hidden]="true" label="Call Number Suffix" i18n-label hidden>
+ </eg-grid-column>
+ <eg-grid-column path="circ_modifier" [hidden]="true" label="Circulation Modifier" i18n-label></eg-grid-column>
+ <eg-grid-column path="circ_lib.shortname" label="Circulation Library" i18n-label></eg-grid-column>
+ <eg-grid-column path="location.name" [hidden]="true" label="Shelving Location" i18n-label></eg-grid-column>
+ <eg-grid-column path="status.name" [hidden]="true" label="Copy Status" i18n-label></eg-grid-column>
+ <eg-grid-column path="relationship" label="Relationship" i18n-label></eg-grid-column>
+ </eg-grid>
+ </div>
</div>
</div>
-</div>
</ng-template>
<ng-template #barcodeCellTemplate let-entry="row">
-<span>
- <a class="pl-1"
- href="/eg/staff/cat/item/{{entry.id()}}">
- {{entry.barcode()}}
- </a>
-</span>
+ <span *ngIf="entry.item()">
+ <a class="pl-1" href="/eg/staff/cat/item/{{entry.item().id()}}">
+ {{entry.item().barcode()}}
+ </a>
+ </span>
</ng-template>
<ng-template #titleCellTemplate let-entry="row">
-<span>
- <a class="pl-1"
- href="/eg/staff/cat/catalog/record/{{entry.call_number().record()}}">
- {{entry._title}}
+ <a class="pl-1" routerLink="/staff/catalog/record/{{entry.record().id()}}">
+ {{entry.record().wide_display_entry().title()}}
</a>
-</span>
</ng-template>
<ng-template #idlClassLabel let-r="result" i18n>
-{{r.label}}
+ {{r.label}}
</ng-template>
<ng-container *ngIf="!isDialog()">
</ng-container>
</ng-container>
-<eg-fm-record-editor #editDialog
- idlClass='acmcm'
- [fieldOptions]="{course: {linkedSearchField: 'course_number'}}"
+<eg-fm-record-editor #editDialog idlClass='acmcm' [fieldOptions]="{course: {linkedSearchField: 'course_number'}}"
[preloadLinkedValues]="true"
hiddenFields="id,item,original_callnumber,original_status,original_location,original_circ_modifier,record">
-</eg-fm-record-editor>
\ No newline at end of file
+</eg-fm-record-editor>
import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
-import {Router, ActivatedRoute} from '@angular/router';
-import {Observable, Observer, of} from 'rxjs';
+import {ActivatedRoute} from '@angular/router';
+import {from, Observable} from 'rxjs';
+import {switchMap} from 'rxjs/operators';
import {DialogComponent} from '@eg/share/dialog/dialog.component';
import {AuthService} from '@eg/core/auth.service';
import {NetService} from '@eg/core/net.service';
import {GridComponent} from '@eg/share/grid/grid.component';
import {IdlObject, IdlService} from '@eg/core/idl.service';
import {StringComponent} from '@eg/share/string/string.component';
-import {StaffBannerComponent} from '@eg/staff/share/staff-banner.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';
templateUrl: './course-associate-material.component.html'
})
-export class CourseAssociateMaterialComponent extends DialogComponent {
+export class CourseAssociateMaterialComponent extends DialogComponent implements OnInit {
@Input() currentCourse: IdlObject;
@Input() courseId: any;
@Input() displayMode: String;
@Input() isModifyingCircMod: Boolean;
@Input() isModifyingCallNumber: Boolean;
@Input() isModifyingLocation: Boolean;
+ bibId: number;
+
+ associateBriefRecord: (newRecord: string) => void;
+ associateElectronicBibRecord: () => void;
constructor(
private auth: AuthService,
) {
super(modal);
this.materialsDataSource = new GridDataSource();
+
+ this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return this.net.request(
+ 'open-ils.courses',
+ 'open-ils.courses.course_materials.retrieve.fleshed',
+ {course: this.courseId}
+ );
+ };
}
ngOnInit() {
- this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
- return this.loadMaterialsGrid(pager);
- }
+ this.associateBriefRecord = (newRecord: string) => {
+ return this.net.request(
+ 'open-ils.courses',
+ 'open-ils.courses.attach.biblio_record',
+ this.auth.token(),
+ newRecord,
+ this.courseId,
+ this.relationshipInput
+ ).subscribe(() => {
+ this.materialsGrid.reload();
+ this.materialAddSuccessString.current()
+ .then(str => this.toast.success(str));
+ });
+ };
+
+ this.associateElectronicBibRecord = () => {
+ return this.net.request(
+ 'open-ils.courses',
+ 'open-ils.courses.attach.electronic_resource',
+ this.auth.token(),
+ this.bibId,
+ this.courseId,
+ this.relationshipInput
+ ).subscribe(() => {
+ this.materialsGrid.reload();
+ this.materialAddSuccessString.current()
+ .then(str => this.toast.success(str));
+ });
+ };
+
}
isDialog(): boolean {
return this.displayMode === 'dialog';
}
- loadMaterialsGrid(pager: Pager): Observable<any> {
- return new Observable<any>(observer => {
- this.course.getMaterials(this.courseId).then(materials => {
- materials.forEach(material => {
- this.course.fleshMaterial(material).then(fleshed_material => {
- this.materialsDataSource.data.push(fleshed_material);
- });
- });
- });
- observer.complete();
- });
- }
-
editSelectedMaterials(itemFields: IdlObject[]) {
// Edit each IDL thing one at a time
const editOneThing = (item: IdlObject) => {
this.materialEditSuccessString.current()
.then(str => this.toast.success(str));
this.pcrud.retrieve('acmcm', result).subscribe(material => {
- if (material.course() != this.courseId) {
+ if (material.course() !== this.courseId) {
this.materialsDataSource.data.splice(
this.materialsDataSource.data.indexOf(course_material, 0), 1
);
);
});
}
-
+
associateItem(barcode, relationship) {
if (barcode) {
- let args = {
+ const args = {
barcode: barcode,
relationship: relationship,
isModifyingCallNumber: this.isModifyingCallNumber,
tempLocation: this.tempLocation,
tempStatus: this.tempStatus,
currentCourse: this.currentCourse
- }
+ };
this.barcodeInput = null;
this.pcrud.search('acp', {barcode: args.barcode}, {
flesh: 3, flesh_fields: {acp: ['call_number']}
}).subscribe(item => {
- let associatedMaterial = this.course.associateMaterials(item, args);
+ const associatedMaterial = this.course.associateMaterials(item, args);
associatedMaterial.material.then(res => {
item = associatedMaterial.item;
let new_cn = item.call_number().label();
- if (this.tempCallNumber) new_cn = this.tempCallNumber;
+ if (this.tempCallNumber) { new_cn = this.tempCallNumber; }
this.course.updateItem(item, this.currentCourse.owning_lib(),
new_cn, args.isModifyingCallNumber
).then(resp => {
- this.course.fleshMaterial(res).then(fleshed_material => {
- this.materialsDataSource.data.push(fleshed_material);
- });
- if (item.circ_lib() != this.currentCourse.owning_lib()) {
+ this.materialsGrid.reload();
+ if (item.circ_lib() !== this.currentCourse.owning_lib()) {
this.materialAddDifferentLibraryString.current()
.then(str => this.toast.warning(str));
} else {
}
deleteSelectedMaterials(items) {
- let item_ids = [];
+ const item_ids = [];
items.forEach(item => {
this.materialsDataSource.data.splice(this.materialsDataSource.data.indexOf(item, 0), 1);
- item_ids.push(item.id())
+ item_ids.push(item.id());
});
this.pcrud.search('acmcm', {course: this.courseId, item: item_ids}).subscribe(material => {
material.isdeleted(true);
);
});
}
-}
\ No newline at end of file
+}
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';
@NgModule({
declarations: [
CourseListComponent,
CoursePageComponent,
CourseAssociateMaterialComponent,
- CourseAssociateUsersComponent
+ CourseAssociateUsersComponent,
],
imports: [
StaffCommonModule,
AdminCommonModule,
CourseReservesRoutingModule,
ItemLocationSelectModule,
+ MarcSimplifiedEditorModule,
TreeModule
],
exports: [
// Pass-through to marcrecord.js
isControlfield(): boolean;
+ indicator?: (ind: number) => any;
deleteExactSubfields(...subfield: MarcSubfield[]): number;
}
--- /dev/null
+import {Component, Host, Input, OnInit} from '@angular/core';
+import {MarcSimplifiedEditorComponent} from './simplified-editor.component';
+import {MarcSubfield} from '../marcrecord';
+
+/**
+ * A field that a user can edit, which will later be
+ * compiled into MARC
+ */
+
+@Component({
+ selector: 'eg-marc-simplified-editor-field',
+ template: '<ng-template></ng-template>'
+})
+export class MarcSimplifiedEditorFieldComponent implements OnInit {
+
+ @Input() tag: string;
+ @Input() subfield: string;
+ @Input() defaultValue: string;
+
+ constructor(@Host() private editor: MarcSimplifiedEditorComponent) {}
+
+ ngOnInit() {
+ this.editor.addField({
+ tag: this.tag,
+ subfields: [[
+ this.subfield,
+ this.defaultValue ? this.defaultValue : '',
+ 0
+ ]],
+ authValid: false,
+ authChecked: false,
+ isCtrlField: false,
+ isControlfield: () => false,
+ indicator: (ind: number) => '0',
+ deleteExactSubfields: (...subfield: MarcSubfield[]) => 0,
+ });
+ }
+
+}
+
+
+
--- /dev/null
+<ng-container *ngIf="editor">
+ <form [formGroup]="editor">
+ <ng-container *ngFor="let field of fields">
+ <div class="row" *ngIf="!field.subfields[0][1]">
+ <div class="col-lg-3">
+ <label for="{{idPrefix}}-{{field.tag}}{{field.subfields[0]}}">
+ {{fieldLabels[field.fieldId]}}
+ </label>
+ </div>
+ <div class="col-lg-9">
+ <input id="{{idPrefix}}-{{field.tag}}{{field.subfields[0]}}" formControlName="{{field.fieldId}}" />
+ </div>
+ </div>
+ </ng-container>
+ <button class="btn btn-primary" (click)="emitXml()">
+ <ng-container *ngIf="buttonLabel">{{buttonLabel}}</ng-container>
+ <ng-container *ngIf="!buttonLabel" i18n>Save</ng-container>
+ </button>
+ </form>
+</ng-container>
+
--- /dev/null
+import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {FormGroup, FormControl, ValidationErrors, ValidatorFn, FormArray} from '@angular/forms';
+import {MarcField, MarcRecord} from '../marcrecord';
+import {TagTableService} from '../tagtable.service';
+
+/**
+ * A simplified editor for basic MARC records, which
+ * does not require knowledge of MARC tags
+ */
+
+@Component({
+ selector: 'eg-marc-simplified-editor',
+ templateUrl: './simplified-editor.component.html'
+})
+export class MarcSimplifiedEditorComponent implements AfterViewInit, OnInit {
+
+ @Input() buttonLabel: string;
+ @Output() xmlRecordEvent = new EventEmitter<string>();
+
+ fields: MarcField[] = [];
+ editor: FormGroup;
+
+ // DOM id prefix to prevent id collisions.
+ idPrefix: string;
+
+ fieldIndex = 0;
+ fieldLabels: string[] = [];
+
+ addField: (field: MarcField) => void;
+
+ constructor(
+ private tagTable: TagTableService
+ ) {}
+
+ ngOnInit() {
+ // Add some randomness to the generated DOM IDs to ensure against clobbering
+ this.idPrefix = 'marc-simplified-editor-' + Math.floor(Math.random() * 100000);
+ this.editor = new FormGroup({});
+
+ // Add a fieldId, and then add a new field to the array
+ this.addField = (field: MarcField) => {
+ field.fieldId = this.fieldIndex;
+ this.fields.push(field);
+ this.editor.addControl(String(this.fieldIndex), new FormControl(null, []));
+ this.fieldIndex++;
+ };
+
+ }
+
+ ngAfterViewInit() {
+ this.tagTable.loadTags({marcRecordType: 'biblio', ffType: 'BKS'}).then(table => {
+ this.fields.forEach((field) => {
+ this.fieldLabels[field.fieldId] = table.getSubfieldLabel(field.tag, field.subfields[0][0]);
+ });
+ });
+ }
+
+ emitXml() {
+ const record = new MarcRecord('<record xmlns="http://www.loc.gov/MARC21/slim"></record>');
+ // need to add the value to field.subfields[0][1]
+ this.fields.forEach((field) => {
+ if (field.subfields[0][1] === '') { // Default value has not been applied
+ field.subfields[0][1] = this.editor.get(String(field.fieldId)).value;
+ }
+ });
+ record.fields = this.fields;
+ this.xmlRecordEvent.emit(record.toXml());
+ }
+
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
+import {MarcSimplifiedEditorComponent} from './simplified-editor.component';
+import {MarcSimplifiedEditorFieldComponent} from './simplified-editor-field.component';
+import {TagTableService} from '../tagtable.service';
+
+@NgModule({
+ declarations: [
+ MarcSimplifiedEditorComponent,
+ MarcSimplifiedEditorFieldComponent,
+ ],
+ imports: [
+ StaffCommonModule,
+ CommonWidgetsModule
+ ],
+ exports: [
+ MarcSimplifiedEditorComponent,
+ MarcSimplifiedEditorFieldComponent,
+ ],
+ providers: [
+ TagTableService
+ ]
+})
+
+export class MarcSimplifiedEditorModule { }
+
return this.toCache('sfcodes', tag, null, list);
}
+ getSubfieldLabel(tag: string, sfCode: string): string {
+ if (!tag || !this.tagMap[tag]) { return null; }
+ const subfieldResults = this.tagMap[tag].subfields.filter(sf => sf.code === sfCode);
+ return subfieldResults.length ? subfieldResults[0].description : null;
+ }
+
+
getFieldTags(): ContextMenuEntry[] {
if (!this.fieldTags) {
$e->commit;
return 1;
}
-__PACKAGE__->register_method(
- method => 'fetch_course_materials',
- autoritative => 1,
- api_name => 'open-ils.circ.course_materials.retrieve',
- signature => q/
- Returns an array of course materials.
- @params args : Supplied object to filter search.
- /);
-
-__PACKAGE__->register_method(
- method => 'fetch_course_materials',
- autoritative => 1,
- api_name => 'open-ils.circ.course_materials.retrieve.fleshed',
- signature => q/
- Returns an array of course materials, each fleshed out with information
- from the item and the course_material object.
- @params args : Supplied object to filter search.
- /);
-
-sub fetch_course_materials {
- my ($self, $conn, $args) = @_;
- my $e = new_editor();
- my $materials = {};
- my %items;
-
- $materials->{list} = $e->search_asset_course_module_course_materials($args);
- return $materials->{list} unless ($self->api_name =~ /\.fleshed/);
-
- # If we want it fleshed out...
- for my $course_material (@{$materials->{list}}) {
- my $material = {};
- $material->{id} = $course_material->id;
- $material->{relationship} = $course_material->relationship;
- $material->{record} = $course_material->record;
- my $copy = $e->retrieve_asset_copy([
- $course_material->item, {
- flesh => 3, flesh_fields => {
- 'acp' => ['call_number'],
- 'acn' => ['record']
- }
- }
- ]);
-
- $material->{item_data} = $copy;
- $material->{volume_data} = $copy->call_number;
- $material->{record_data} = $copy->call_number->record;
- $items{$course_material->item} = $material;
- }
-
- my $targets = ();
- for my $item (values %items) {
- my $final_item = {};
- my $mvr = $U->record_to_mvr($item->{record_data});
- $final_item->{id} = $item->{id};
- $final_item->{relationship} = $item->{relationship};
- $final_item->{record} = $item->{record};
- $final_item->{barcode} = $item->{item_data}->barcode;
- $final_item->{circ_lib} = $item->{item_data}->circ_lib;
- $final_item->{title} = $mvr->title;
- $final_item->{call_number} = $item->{volume_data}->label;
- $final_item->{location} = $e->retrieve_asset_copy_location(
- $item->{item_data}->location
- );
- $final_item->{status} = $e->retrieve_config_copy_status(
- $item->{item_data}->status
- );
-
- push @$targets, $final_item;
- }
-
- return $targets;
-}
-
-__PACKAGE__->register_method(
- method => 'fetch_courses',
- autoritative => 1,
- api_name => 'open-ils.circ.courses.retrieve',
- signature => q/
- Returns an array of course materials.
- @params course_id: The id of the course we want to retrieve
- /);
-
-sub fetch_courses {
- my ($self, $conn, @course_ids) = @_;
- my $e = new_editor();
-
- return unless @course_ids;
- my $targets = ();
- foreach my $course_id (@course_ids) {
- my $target = $e->retrieve_asset_course_module_course($course_id);
- push @$targets, $target;
- }
-
- return $targets;
-}
-
-__PACKAGE__->register_method(
- method => 'fetch_course_users',
- autoritative => 1,
- api_name => 'open-ils.circ.course_users.retrieve',
- signature => q/
- Returns an array of course users.
- @params course_id: The id of the course we want to retrieve from
- /);
-__PACKAGE__->register_method(
- method => 'fetch_course_users',
- autoritative => 1,
- api_name => 'open-ils.circ.course_users.retrieve.staff',
- signature => q/
- Returns an array of course users.
- @params course_id: The id of the course we want to retrieve from
- /);
-
-sub fetch_course_users {
- my ($self, $conn, $course_id) = @_;
- my $e = new_editor();
- my $filter = {};
- my $users = {};
- my %patrons;
-
- $filter->{course} = $course_id;
- $filter->{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'}});
- for my $course_user (@{$users->{list}}) {
- my $patron = {};
- $patron->{id} = $course_user->id;
- $patron->{usr_role} = $course_user->usr_role;
- $patron->{patron_data} = $e->retrieve_actor_user($course_user->usr);
- $patrons{$course_user->usr} = $patron;
- }
-
- my $targets = ();
- for my $user (values %patrons) {
- my $final_user = {};
- $final_user->{id} = $user->{id};
- $final_user->{usr_role} = $user->{usr_role};
- $final_user->{patron_id} = $user->{patron_data}->id;
- $final_user->{first_given_name} = $user->{patron_data}->first_given_name;
- $final_user->{second_given_name} = $user->{patron_data}->second_given_name;
- $final_user->{family_name} = $user->{patron_data}->family_name;
- $final_user->{pref_first_given_name} = $user->{patron_data}->pref_first_given_name;
- $final_user->{pref_family_name} = $user->{patron_data}->pref_family_name;
- $final_user->{pref_second_given_name} = $user->{patron_data}->pref_second_given_name;
- $final_user->{pref_suffix} = $user->{patron_data}->pref_suffix;
- $final_user->{pref_prefix} = $user->{patron_data}->pref_prefix;
-
- push @$targets, $final_user;
- }
-
- return $targets;
-
-}
__PACKAGE__->register_method(
method => 'fetch_copy_tags',
--- /dev/null
+package OpenILS::Application::Courses;
+
+use strict;
+use warnings;
+
+use OpenSRF::AppSession;
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+my $U = "OpenILS::Application::AppUtils";
+
+use OpenSRF::Utils::Logger qw/$logger/;
+
+__PACKAGE__->register_method(
+ method => 'attach_electronic_resource_to_course',
+ api_name => 'open-ils.courses.attach.electronic_resource',
+ signature => {
+ desc => 'Attaches a bib record for an electronic resource to a course',
+ params => [
+ {desc => 'Authentication token', type => 'string'},
+ {desc => 'Record id', type => 'number'},
+ {desc => 'Course id', type => 'number'},
+ {desc => 'Relationship', type => 'string'}
+ ],
+ return => {desc => '1 on success, event on failure'}
+ });
+sub attach_electronic_resource_to_course {
+ my ($self, $conn, $authtoken, $record, $course, $relationship) = @_;
+ my $e = new_editor(authtoken=>$authtoken, xact=>1);
+ return $e->die_event unless $e->checkauth;
+ return $e->die_event unless
+ $e->allowed('MANAGE_RESERVES');
+
+ my $located_uris = $e->search_asset_call_number({
+ record => $record,
+ deleted => 'f',
+ label => '##URI##' })->[0];
+ my $bib = $e->retrieve_biblio_record_entry([
+ $record, {
+ flesh => 1,
+ flesh_fields => {'bre' => ['source']}
+ }
+ ]);
+ return $e->event unless (($bib->source && $bib->source->transcendant) || $located_uris);
+ _attach_bib($e, $course, $record, $relationship);
+
+ return 1;
+}
+
+__PACKAGE__->register_method(
+ method => 'attach_brief_bib_to_course',
+ api_name => 'open-ils.courses.attach.biblio_record',
+ signature => {
+ desc => 'Creates a new bib record with the provided XML, and attaches it to a course',
+ params => [
+ {desc => 'Authentication token', type => 'string'},
+ {desc => 'XML', type => 'string'},
+ {desc => 'Course id', type => 'number'},
+ {desc => 'Relationship', type => 'string'}
+ ],
+ return => {desc => '1 on success, event on failure'}
+ });
+sub attach_brief_bib_to_course {
+ my ($self, $conn, $authtoken, $marcxml, $course, $relationship) = @_;
+ my $e = new_editor(authtoken=>$authtoken, xact=>1);
+ return $e->die_event unless $e->checkauth;
+ return $e->die_event unless $e->allowed('MANAGE_RESERVES');
+ return $e->die_event unless $e->allowed('CREATE_MARC');
+
+ my $bib_source_id = $U->ou_ancestor_setting_value($self->{ou}, 'circ.course_materials_brief_record_bib_source');
+ my $bib_source_name;
+ if ($bib_source_id) {
+ $bib_source_name = $e->retrieve_config_bib_source($bib_source_id)->source;
+ } else {
+ # The default value from the seed data
+ $bib_source_name = 'Course materials module';
+ }
+
+ my $bib_create = OpenSRF::AppSession
+ ->create('open-ils.cat')
+ ->request('open-ils.cat.biblio.record.xml.create',
+ $authtoken, $marcxml, $bib_source_name)
+ ->gather(1);
+ _attach_bib($e, $course, $bib_create->id, $relationship) if ($bib_create);
+ return 1;
+}
+
+# Shared logic for both e-resources and brief bibs
+sub _attach_bib {
+ my ($e, $course, $record, $relationship) = @_;
+ my $acmcm = Fieldmapper::asset::course_module_course_materials->new;
+ $acmcm->course($course);
+ $acmcm->record($record);
+ $acmcm->relationship($relationship);
+ $e->create_asset_course_module_course_materials( $acmcm ) or return $e->die_event;
+ $e->commit;
+}
+
+sub detach_material_from_course {
+ my ($self, $conn, $authtoken, $acmcm) = @_;
+
+}
+
+__PACKAGE__->register_method(
+ method => 'fetch_course_materials',
+ autoritative => 1,
+ stream => 1,
+ api_name => 'open-ils.courses.course_materials.retrieve',
+ signature => q/
+ Returns an array of course materials.
+ @params args : Supplied object to filter search.
+ /);
+
+__PACKAGE__->register_method(
+ method => 'fetch_course_materials',
+ autoritative => 1,
+ stream => 1,
+ api_name => 'open-ils.courses.course_materials.retrieve.fleshed',
+ signature => q/
+ Returns an array of course materials, each fleshed out with information
+ from the item and the course_material object.
+ @params args : Supplied object to filter search.
+ /);
+
+sub fetch_course_materials {
+ my ($self, $conn, $args) = @_;
+ my $e = new_editor();
+ my $materials;
+
+ if ($self->api_name =~ /\.fleshed/) {
+ my $fleshing = {
+ 'flesh' => 2, 'flesh_fields' => {
+ 'acmcm' => ['item', 'record'],
+ 'acp' => ['call_number', 'circ_lib', 'location', 'status'],
+ 'bre' => ['wide_display_entry'],
+ }
+ };
+ $materials = $e->search_asset_course_module_course_materials([$args, $fleshing]);
+ } else {
+ $materials = $e->search_asset_course_module_course_materials($args);
+ }
+ $conn->respond($_) for @$materials;
+ return undef;
+}
+
+__PACKAGE__->register_method(
+ method => 'fetch_courses',
+ autoritative => 1,
+ api_name => 'open-ils.courses.courses.retrieve',
+ signature => q/
+ Returns an array of course materials.
+ @params course_id: The id of the course we want to retrieve
+ /);
+
+sub fetch_courses {
+ my ($self, $conn, @course_ids) = @_;
+ my $e = new_editor();
+
+ return unless @course_ids;
+ my $targets = ();
+ foreach my $course_id (@course_ids) {
+ my $target = $e->retrieve_asset_course_module_course($course_id);
+ push @$targets, $target;
+ }
+
+ return $targets;
+}
+
+__PACKAGE__->register_method(
+ method => 'fetch_course_users',
+ autoritative => 1,
+ api_name => 'open-ils.courses.course_users.retrieve',
+ signature => q/
+ Returns an array of course users.
+ @params course_id: The id of the course we want to retrieve from
+ /);
+__PACKAGE__->register_method(
+ method => 'fetch_course_users',
+ autoritative => 1,
+ api_name => 'open-ils.courses.course_users.retrieve.staff',
+ signature => q/
+ Returns an array of course users.
+ @params course_id: The id of the course we want to retrieve from
+ /);
+
+sub fetch_course_users {
+ my ($self, $conn, $course_id) = @_;
+ my $e = new_editor();
+ my $filter = {};
+ my $users = {};
+ my %patrons;
+
+ $filter->{course} = $course_id;
+ $filter->{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'}});
+ for my $course_user (@{$users->{list}}) {
+ my $patron = {};
+ $patron->{id} = $course_user->id;
+ $patron->{usr_role} = $course_user->usr_role;
+ $patron->{patron_data} = $e->retrieve_actor_user($course_user->usr);
+ $patrons{$course_user->usr} = $patron;
+ }
+
+ my $targets = ();
+ for my $user (values %patrons) {
+ my $final_user = {};
+ $final_user->{id} = $user->{id};
+ $final_user->{usr_role} = $user->{usr_role};
+ $final_user->{patron_id} = $user->{patron_data}->id;
+ $final_user->{first_given_name} = $user->{patron_data}->first_given_name;
+ $final_user->{second_given_name} = $user->{patron_data}->second_given_name;
+ $final_user->{family_name} = $user->{patron_data}->family_name;
+ $final_user->{pref_first_given_name} = $user->{patron_data}->pref_first_given_name;
+ $final_user->{pref_family_name} = $user->{patron_data}->pref_family_name;
+ $final_user->{pref_second_given_name} = $user->{patron_data}->pref_second_given_name;
+ $final_user->{pref_suffix} = $user->{patron_data}->pref_suffix;
+ $final_user->{pref_prefix} = $user->{patron_data}->pref_prefix;
+
+ push @$targets, $final_user;
+ }
+
+ return $targets;
+
+}
+
+
+
+1;
+
unless $course_id and $course_id =~ /^\d+$/;
$ctx->{course} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.courses.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.courses.retrieve',
[$course_id]
)->[0];
$ctx->{instructors} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.course_users.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.course_users.retrieve',
$course_id
);
$ctx->{course_materials} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.course_materials.retrieve.fleshed',
+ 'open-ils.courses',
+ 'open-ils.courses.course_materials.retrieve.fleshed.atomic',
{course => $course_id}
);
return Apache2::Const::OK;
}
return ($full_query, @queries);
-}
\ No newline at end of file
+}
);
if ($ctx->{course_module_opt_in}) {
$copy->{course_materials} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.course_materials.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.course_materials.retrieve',
{item => $copy->{id}}
);
my %course_ids;
}
$copy->{courses} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.courses.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.courses.retrieve',
keys %course_ids
);
}
$rec->{popularity} = $res_rec->[2];
if ($course_module_opt_in) {
$rec->{course_materials} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.course_materials.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.course_materials.retrieve',
{record => $rec->{id}}
);
my %course_ids;
}
$rec->{courses} = $U->simplereq(
- 'open-ils.circ',
- 'open-ils.circ.courses.retrieve',
+ 'open-ils.courses',
+ 'open-ils.courses.courses.retrieve',
keys %course_ids
);
}
return ($cache_key, $list);
}
-1;
\ No newline at end of file
+1;
--- /dev/null
+#!perl
+
+use Test::More tests => 1;
+
+diag("Test the course materials module.");
+
+use strict; use warnings;
+
+use OpenILS::Utils::TestUtils;
+my $script = OpenILS::Utils::TestUtils->new();
+$script->bootstrap;
+
+our $apputils = "OpenILS::Application::AppUtils";
+
+is(1, 1, 'placeholder');
+
+
+# Test: can attach a bib record with located URI
+# Test: cannot attach a bib record without a located URI
+
+# Test: can detach an item (just delete this)
+# Test: can detach a record that is not temporary (just delete this)
+# Test: can detach a record that is temporary (delete this, and delete the record too)
CREATE TABLE asset.course_module_course_materials (
id SERIAL PRIMARY KEY,
course INT NOT NULL REFERENCES asset.course_module_course (id),
- item INT NOT NULL REFERENCES asset.copy (id),
+ 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,
- unique (course, item)
-);
-
-CREATE TABLE asset.course_module_non_cat_course_materials (
- id SERIAL PRIMARY KEY,
- course INT NOT NULL REFERENCES asset.course_module_course (id),
- item TEXT NOT NULL,
- url TEXT,
- relationship TEXT
+ unique (course, item, record)
);
COMMIT;
'coust', 'description'),
'bool', null)
+,( 'circ.course_materials_brief_record_bib_source', 'circ',
+ oils_i18n_gettext(
+ 'circ.course_materials_brief_record_bib_source',
+ 'Bib source for brief records created in the course materials module',
+ 'coust', 'label'),
+ oils_i18n_gettext(
+ 'circ.course_materials_brief_record_bib_source',
+ 'The course materials module will use this bib source for any new brief bibliographic records made inside that module. For best results, use a transcendant bib source.',
+ 'coust', 'description'),
+ 'link', 'cbs')
+
,( 'circ.password_reset_request_per_user_limit', 'sec',
oils_i18n_gettext('circ.password_reset_request_per_user_limit',
'cwst', 'label'
)
);
+INSERT INTO config.bib_source (quality, source, transcendant) VALUES
+ (1, oils_i18n_gettext(1, 'Course materials module', 'cbs', 'source'), TRUE);
+
+INSERT INTO actor.org_unit_setting (org_unit, name, value)
+ SELECT 1, 'circ.course_materials_brief_record_bib_source', id
+ FROM config.bib_source
+ WHERE source='Course materials module';
+
INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
VALUES (
CREATE TABLE asset.course_module_course_materials (
id SERIAL PRIMARY KEY,
course INT NOT NULL REFERENCES asset.course_module_course (id),
- item INT NOT NULL REFERENCES asset.copy (id),
+ 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,
- unique (course, item)
-);
-
-CREATE TABLE asset.course_module_non_cat_course_materials (
- id SERIAL PRIMARY KEY,
- course INT NOT NULL REFERENCES asset.course_module_course (id),
- item TEXT NOT NULL,
- url TEXT,
- relationship TEXT
+ unique (course, item, record)
);
INSERT INTO permission.perm_list(id, code, description)
INSERT INTO permission.grp_perm_map(perm, grp, depth) VALUES (624, 9, 0), (624, 11, 0), (624, 12, 0), (624, 13, 0);
INSERT INTO config.org_unit_setting_type
- (grp, name, datatype, label, description)
+ (grp, name, datatype, label, description, fm_class)
VALUES (
'circ',
'circ.course_materials_opt_in', 'bool',
'If enabled, the Org Unit will utilize Course Material functionality.'
'coust',
'description'
- )
+ ), null
), (
'circ',
'circ.course_materials_browse_by_instructor', 'bool',
'If enabled, the Org Unit will allow OPAC users to browse Courses by instructor name.'
'coust',
'description'
- )
+ ), null
+), (
+ 'circ',
+ 'circ.course_materials_brief_record_bib_source', 'link',
+ oils_i18n_gettext(
+ 'circ.course_materials_brief_record_bib_source',
+ 'Bib source for brief records created in the course materials module',
+ 'coust', 'label'
+ ),
+ oils_i18n_gettext(
+ 'circ.course_materials_brief_record_bib_source',
+ 'The course materials module will use this bib source for any new brief bibliographic records made inside that module. For best results, use a transcendant bib source.',
+ 'coust', 'description'
+ ), 'cbs'
+
);
+INSERT INTO config.bib_source (quality, source, transcendant) VALUES
+ (1, oils_i18n_gettext(1, 'Course materials module', 'cbs', 'source'), TRUE);
+
+INSERT INTO actor.org_unit_setting (org_unit, name, value)
+ SELECT 1, 'circ.course_materials_brief_record_bib_source', id
+ FROM config.bib_source
+ WHERE source='Course materials module';
+
COMMIT;
</tr>
</thead>
<tbody>
- [% FOREACH copy_info IN ctx.course_materials %]
+ [% FOREACH material IN ctx.course_materials %]
<tr>
- <td>[%- INCLUDE "opac/parts/library_name_link.tt2"; -%]
- <link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut">
- <meta property="price" content="0.00">
+ <td>
+ [% IF material.item %]
+ [%- fleshed_ou = material.item.circ_lib -%]
+ [%- INCLUDE "opac/parts/library_name_link_from_ou.tt2"; -%]
+ [% ELSE %]
+ [% l('Online') %]
+ [% END %]
+ <link property="businessFunction" href="http://purl.org/goodrelations/v1#LeaseOut">
+ <meta property="price" content="0.00">
+ </td>
+ <td>
+ [% IF material.item %]
+ [% material.item.call_number.label %]
+ [% END %]
</td>
- <td>[% copy_info.call_number %]</td>
<td>
- <a href="[% mkurl(ctx.opac_root _ '/record/' _ copy_info.record) %]">
- [% copy_info.title %]
+ <a href="[% mkurl(ctx.opac_root _ '/record/' _ material.record.id) %]">
+ [% material.record.wide_display_entry.title %]
</a>
</td>
- <td>[% copy_info.barcode %]</td>
- <td>[% copy_info.relationship %]</td>
- <td>[% copy_info.status.name %]</td>
- <td>[% copy_info.location.name %]</td>
+ <td>
+ [% IF material.item %]
+ [% material.item.barcode %]
+ [% END %]
+ </td>
+ <td> [% material.relationship %] </td>
+ <td>
+ [% IF material.item %]
+ [% material.item.status.name %]
+ [% END %]
+ </td>
+ <td>
+ [% IF material.item %]
+ [% material.item.location.name %]
+ [% END %]
+ </td>
</tr>
[% END %]
</tbody>