<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="user_role" reporter:datatype="text" />
+ <field reporter:label="User Role" name="usr_role" reporter:datatype="text" />
+ <field reporter:label="Is Public Viewable?" name="is_public" reporter:datatype="boolean" />
</fields>
<links>
<link field="course" reltype="has_a" key="id" map="" class="acmc" />
<field reporter:label="ID" name="id" reporter:datatype="id" />
<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="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" />
<links>
<link field="course" reltype="has_a" key="id" map="" class="acmc" />
<link field="item" reltype="has_a" key="id" map="" class="acp" />
+ <link field="record" reltype="has_a" key="id" map="" class="bre" />
<link field="original_callnumber" reltype="has_a" key="id" map="" class="acn" />
<link field="original_status" reltype="has_a" key="id" map="" class="ccs" />
<link field="original_circ_modifier" reltype="has_a" key="code" map="" class="ccm" />
<div class="input-group-prepend">
<span class="input-group-text" i18n>Barcode</span>
</div>
- <input type="text" [(ngModel)]="barcodeInput" />
+ <input type="text" [(ngModel)]="barcodeInput"
+ (click)="$event.target.select()"
+ (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
</div>
</div>
<div class="col-md-5">
</div>
</div>
<eg-item-location-select permFilter="MANAGE_RESERVES"
- [(ngModel)]="tempLocation" (oninput)="isModifyingLocation = true">
+ [(ngModel)]="tempLocation" (oninput)="isModifyingLocation = true"
+ (valueChange)="isModifyingLocation = true">
</eg-item-location-select>
<div class="input-group-append">
<div class="input-group-text">
tempStatus: this.tempStatus,
currentCourse: this.currentCourse
}
+ this.barcodeInput = null;
- this.pcrud.search('acp', {barcode: barcode}, {
+ this.pcrud.search('acp', {barcode: args.barcode}, {
flesh: 3, flesh_fields: {acp: ['call_number']}
}).subscribe(item => {
let associatedMaterial = this.courseSvc.associateMaterials(item, args);
- console.log(associatedMaterial);
associatedMaterial.material.then(res => {
item = associatedMaterial.item;
let new_cn = item.call_number().label();
<eg-string #successString i18n-text text="{{table_name}} Update Succeeded"></eg-string>
<eg-string #createString i18n-text text="{{table_name}} Was Created Successfully"></eg-string>
-<eg-string #deleteFailedString i18n-text text="Delete of {{table_name}} failed or was not allowed"></eg-string>
-<eg-string #deleteSucailedString i18n-text text="Delete of {{table_name}} failed or was not allowed"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Deletion of {{table_name}} failed or was not allowed"></eg-string>
+<eg-string #deleteSucailedString i18n-text text="Deletion of {{table_name}} was successful"></eg-string>
<eg-string #archiveFailedString i18n-text text="Archival of {{table_name}} failed or was not allowed"></eg-string>
<eg-string #archiveSuccessString i18n-text text="Archival of {{table_name}} succeeded"></eg-string>
<eg-string #flairTooltip i18n-text text="Limited Editing"></eg-string>
import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
import {IdlObject} from '@eg/core/idl.service';
import {PcrudService} from '@eg/core/pcrud.service';
import {AuthService} from '@eg/core/auth.service';
private net: NetService,
private org: OrgService,
private pcrud: PcrudService,
- private toast: ToastService,
+ private route: ActivatedRoute,
+ private router: Router,
+ private toast: ToastService
){}
ngOnInit() {
this.getSource();
+ this.grid.onRowActivate.subscribe((course:IdlObject) => {
+ let idToEdit = course.id();
+ this.navigateToCoursePage(idToEdit);
+ })
}
/**
};
}
+ navigateToCoursePage(id: any) {
+ this.router.navigate(["/staff/admin/local/asset/course_list/" + id]);
+ }
+
showEditDialog(standingPenalty: IdlObject): Promise<any> {
this.editDialog.mode = 'update';
this.editDialog.recordId = standingPenalty['id']();
--- /dev/null
+<eg-staff-banner
+ bannerText=" {{currentCourse.course_number()}}: {{currentCourse.name()}}"
+ i18n-bannerText class="mb-3" *ngIf="currentCourse"
+ [bannerStyle]="currentCourse.is_archived() == 't' ? 'alert-secondary' : null"
+ [bannerIcon]="currentCourse.is_archived() == 't' ? 'lock' : null">
+</eg-staff-banner>
+<ngb-tabset class="mb-3">
+
+ <!-- Edit Tab -->
+ <ngb-tab title="Edit Course" i18n-title id="edit">
+ <ng-template ngbTabContent>
+ <div class="row">
+ <div class="col-lg-3 mt-3">
+ <button class="p-2 mb-3 btn btn-danger btn-lg"
+ (click)="archiveCourse()" [disabled]="currentCourse && currentCourse.is_archived() == 't'">
+ <i class="material-icons align-middle"
+ *ngIf="currentCourse && currentCourse.is_archived() == 't'">block</i>
+ <span class="align-middle">Archive Course</span>
+ </button>
+ </div>
+ <div class="col-lg-6 mt-3">
+ <eg-fm-record-editor displayMode="inline"
+ hiddenFieldsList="id,is_archived"
+ idlClass="acmc"
+ [preloadLinkedValues]="true"
+ [record]="currentCourse">
+ </eg-fm-record-editor>
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+
+ <!-- Materials Tab -->
+ <ngb-tab title="Course Materials" i18n-title id="courseMaterials">
+ <ng-template ngbTabContent>
+ <div class="row mt-3">
+ <!-- Input Sidebar -->
+ <div class="col-lg-4 mt-3">
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <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>
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <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 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="col-lg-12 d-flex">
+ <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>
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <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>
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <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>
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <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>
+ <!-- End Input Sidebar -->
+ <div class="col-lg-8 mt-3">
+ <eg-grid #materialsGrid [dataSource]="materialsDataSource">
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($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="barcode" [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>
+ <ng-template #barcodeCellTemplate let-entry="row">
+ <span>
+ <a class="pl-1"
+ href="/eg/staff/cat/item/{{entry.id()}}">
+ {{entry.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>
+ </span>
+ </ng-template>
+ <ng-template #idlClassLabel let-r="result" i18n>
+ {{r.label}}
+ </ng-template>
+ </ng-template>
+ </ngb-tab>
+
+ <!-- Users Tab -->
+ <ngb-tab title="Course Users" i18n-title id="courseUsers">
+ <ng-template ngbTabContent>
+ <div class="row mt-3">
+ <div class="col-lg-4 mt-3">
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Patron Barcode</span>
+ </div>
+ <input type="text" class="flex-grow-1" [(ngModel)]="userBarcode"
+ (click)="$event.target.select()"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'" />
+ <!--(keyup.enter)="associateUser(userBarcode)"-->
+ </div>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col-lg-12 d-flex">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Role</span>
+ </div>
+ <input type="text" [(ngModel)]="userRoleInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ placeholder-i18n placeholder="e.g. Student, TA, Instructor..."
+ class="flex-grow-1" />
+ </div>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col-lg-12 text-right">
+ <!--(click)="associateUser(userBarcode)"-->
+ <button class="btn btn-primary"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ i18n [disabled]="!userBarcode">
+ Add User
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-8 mt-3">
+ <!-- eg-grid -->
+ </div>
+ </div>
+ </ng-template>
+ </ngb-tab>
+</ngb-tabset>
+
+<eg-string #archiveFailedString i18n-text text="Archival of Course failed or was not allowed"></eg-string>
+<eg-string #archiveSuccessString i18n-text text="Archival of Course succeeded"></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 #MaterialAddDifferentLibraryString i18n-text text="Material exists at a different library"></eg-string>
\ No newline at end of file
--- /dev/null
+import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
+import {Router, ActivatedRoute} from '@angular/router';
+import {Observable, Observer, of} from 'rxjs';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {Pager} from '@eg/share/util/pager';
+import {NgbModal, NgbModalOptions} 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 {StringComponent} from '@eg/share/string/string.component';
+import {StaffBannerComponent} from '@eg/staff/share/staff-banner.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {CourseService} from '@eg/staff/share/course.service';
+
+@Component({
+ selector: 'eg-course-page',
+ templateUrl: './course-page.component.html'
+})
+
+export class CoursePageComponent implements OnInit {
+
+ currentCourse: IdlObject;
+ courseId: any;
+
+ // Edit Tab
+ @ViewChild('archiveFailedString', { static: true })
+ archiveFailedString: StringComponent;
+ @ViewChild('archiveSuccessString', { static: true })
+ archiveSuccessString: StringComponent;
+
+ // Materials Tab
+ materials: any[] = [];
+ @ViewChild('materialsGrid', {static: true}) materialsGrid: GridComponent;
+ @ViewChild('materialDeleteFailedString', { static: true })
+ materialDeleteFailedString: StringComponent;
+ @ViewChild('materialDeleteSuccessString', { static: true })
+ materialDeleteSuccessString: StringComponent;
+ @ViewChild('materialAddSuccessString', { static: true })
+ materialAddSuccessString: StringComponent;
+ @ViewChild('materialAddFailedString', { static: true })
+ materialAddFailedString: StringComponent;
+ @ViewChild('materialAddDifferentLibraryString', { static: true })
+ materialAddDifferentLibraryString: StringComponent;
+ materialsDataSource: GridDataSource;
+ @Input() barcodeInput: String;
+ @Input() relationshipInput: String;
+ @Input() tempCallNumber: String;
+ @Input() tempStatus: Number;
+ @Input() tempLocation: Number;
+ @Input() tempCircMod: String;
+ @Input() isModifyingStatus: Boolean;
+ @Input() isModifyingCircMod: Boolean;
+ @Input() isModifyingCallNumber: Boolean;
+ @Input() isModifyingLocation: Boolean;
+
+ // Users Tab
+ @Input() userBarcode: String;
+ @Input() userRoleInput: String;
+ constructor(
+ private auth: AuthService,
+ private course: CourseService,
+ private event: EventService,
+ private idl: IdlService,
+ private org: OrgService,
+ private pcrud: PcrudService,
+ private route: ActivatedRoute,
+ private toast: ToastService
+ ) {
+ this.materialsDataSource = new GridDataSource();
+ }
+
+ ngOnInit() {
+ this.courseId = parseInt(this.route.snapshot.paramMap.get('id'));
+ this.course.getCourses([this.courseId]).then(course => {
+ this.currentCourse = course[0];
+ });
+ this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return this.loadMaterialsGrid(pager);
+ }
+ }
+
+ // Edit Tab
+ archiveCourse() {
+ this.course.disassociateMaterials([this.currentCourse]).then(res => {
+ this.currentCourse.is_archived('t');
+ this.pcrud.update(this.currentCourse).subscribe(val => {
+ console.debug('archived: ' + val);
+ this.archiveSuccessString.current()
+ .then(str => this.toast.success(str));
+ }, err => {
+ this.archiveFailedString.current()
+ .then(str => this.toast.danger(str));
+ });
+ });
+ }
+
+ // Materials Tab
+ loadMaterialsGrid(pager: Pager): Observable<any> {
+ return new Observable<any>(observer => {
+ this.course.getMaterials(this.courseId).then(materials => {
+ materials.forEach(material => {
+ this.course.fleshMaterial(material.item(), material.relationship()).then(fleshed_material => {
+ this.materialsDataSource.data.push(fleshed_material);
+ });
+ });
+ });
+ observer.complete();
+ });
+ }
+
+ associateItem(barcode, relationship) {
+ if (barcode) {
+ let args = {
+ barcode: barcode,
+ relationship: relationship,
+ isModifyingCallNumber: this.isModifyingCallNumber,
+ isModifyingCircMod: this.isModifyingCircMod,
+ isModifyingLocation: this.isModifyingLocation,
+ isModifyingStatus: this.isModifyingStatus,
+ tempCircMod: this.tempCircMod,
+ 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);
+ associatedMaterial.material.then(res => {
+ item = associatedMaterial.item;
+ let new_cn = item.call_number().label();
+ if (this.tempCallNumber) new_cn = this.tempCallNumber;
+ this.course.updateItem(item, this.currentCourse.owning_lib(),
+ new_cn, args.isModifyingCallNumber
+ ).then(resp => {
+ this.course.fleshMaterial(item.id(), args.relationship).then(fleshed_material => {
+ this.materialsDataSource.data.push(fleshed_material);
+ });
+ if (item.circ_lib() != this.currentCourse.owning_lib()) {
+ this.materialAddDifferentLibraryString.current()
+ .then(str => this.toast.warning(str));
+ } else {
+ this.materialAddSuccessString.current()
+ .then(str => this.toast.success(str));
+ }
+ });
+ }, err => {
+ this.materialAddFailedString.current()
+ .then(str => this.toast.danger(str));
+ });
+ });
+ }
+ }
+
+ deleteSelected(items) {
+ let item_ids = [];
+ items.forEach(item => {
+ this.materialsDataSource.data.splice(this.materialsDataSource.data.indexOf(item, 0), 1);
+ item_ids.push(item.id())
+ });
+ this.pcrud.search('acmcm', {course: this.courseId, item: item_ids}).subscribe(material => {
+ material.isdeleted(true);
+ this.pcrud.autoApply(material).subscribe(
+ val => {
+ this.course.resetItemFields(material, this.currentCourse.owning_lib());
+ console.debug('deleted: ' + val);
+ this.materialDeleteSuccessString.current().then(str => this.toast.success(str));
+ },
+ err => {
+ this.materialDeleteFailedString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ });
+ }
+}
\ No newline at end of file
import {StaffCommonModule} from '@eg/staff/common.module';
import {AdminCommonModule} from '@eg/staff/admin/common.module';
import {CourseListComponent} from './course-list.component';
+import {CoursePageComponent} from './course-page.component';
import {CourseAssociateMaterialComponent} from './course-associate-material.component';
import {CourseReservesRoutingModule} from './routing.module';
import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
@NgModule({
declarations: [
CourseListComponent,
+ CoursePageComponent,
CourseAssociateMaterialComponent
],
imports: [
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {CourseListComponent} from './course-list.component';
+import {CoursePageComponent} from './course-page.component';
const routes: Routes = [{
+ path: ':id',
+ component: CoursePageComponent
+}, {
path: '',
component: CourseListComponent
}];
</ng-template>
<ng-template #courseTemplate let-copy="row">
- <div *ngFor="let course of copy._courses">{{course.course_number()}}</div>
+ <div *ngFor="let course of copy._courses">
+ <a routerLink="/staff/admin/local/asset/course_list/{{course.id()}}">
+ {{course.course_number()}}
+ </a>
+ </div>
</ng-template>
<div class='eg-copies w-100 mt-3'>
</ng-container>
<ng-container *ngIf="has_course">
<div i18n>Associated Courses:
- {{courseNames.join(', ')}}</div>
+ <span *ngFor="let course of courses; let isLast=last">
+ <a routerLink="/staff/admin/local/asset/course_list/{{course.id()}}">
+ {{course.name()}} ({{course.course_number()}})
+ </a>{{isLast ? '' : ', '}}
+ </span>
+ </div>
</ng-container>
</div>
</div>
isRecordSelected: boolean;
basketSub: Subscription;
has_course: boolean;
- courseNames: any[] = [];
+ courses: any[] = [];
constructor(
private router: Router,
if (res) {
this.course.fetchCopiesInCourseFromRecord(recordId).then(course_list => {
Object.keys(course_list).forEach(key => {
- this.courseNames.push(course_list[key].name() +
- "(" + course_list[key].course_number() + ")");
+ this.courses.push(course_list[key]);
});
this.has_course = true;
});
<li class="list-group-item" *ngFor="let course of courses">
<div class="d-flex">
<div class="flex-1 font-weight-bold" i18n>Course Name:</div>
- <div class="flex-3">{{course.name()}}</div>
+ <div class="flex-3">
+ <a routerLink="/staff/admin/local/asset/course_list/{{course.id()}}">
+ {{course.name()}}
+ </a>
+ </div>
<div class="flex-1 font-weight-bold" i18n>Course Number:</div>
<div class="flex-1">{{course.course_number()}}</div>
<div class="flex-1 font-weight-bold" i18n>Section Number:</div>
}
}
+ getMaterials(course_ids?: Number[]): Promise<IdlObject[]> {
+ if (!course_ids) {
+ return this.pcrud.retrieveAll('acmcm',
+ {}, {atomic: true}).toPromise();
+ } else {
+ return this.pcrud.search('acmcm', {course: course_ids},
+ {}, {atomic: true}).toPromise();
+ }
+ }
+
+ fleshMaterial(itemId, relationship?): Promise<any> {
+ return new Promise((resolve, reject) => {
+ let item = this.idl.create('acp');
+ this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.copy_details.retrieve',
+ this.auth.token(), itemId
+ ).subscribe(res => {
+ if (res && res.copy) {
+ item = res.copy;
+ item.call_number(res.volume);
+ item._title = res.mvr.title();
+ item.circ_lib(this.org.get(item.circ_lib()));
+ if (relationship) item._relationship = relationship;
+ }
+ }, err => {
+ reject(err);
+ }, () => resolve(item));
+ });
+ }
+
getCoursesFromMaterial(copy_id): Promise<any> {
let id_list = [];
return new Promise((resolve, reject) => {
id_list.push(materials.course());
}
}, err => {
- console.log(err);
+ console.debug(err);
reject(err);
}, () => {
if (id_list.length) {
// Creating a new acmcm Entry
associateMaterials(item, args) {
- console.log("entering associateMaterials")
let material = this.idl.create('acmcm');
material.item(item.id());
+ if (item.call_number() && item.call_number().record()) {
+ material.record(item.call_number().record());
+ }
material.course(args.currentCourse.id());
if (args.relationship) material.relationship(args.relationship);
material.isdeleted(true);
this.resetItemFields(material, course_library_hash[material.course()]);
this.pcrud.autoApply(material).subscribe(res => {
- console.log(res);
}, err => {
reject(err);
}, () => {
resolve(item);
});
} else {
- return this.pcrud.update(item);
+ this.pcrud.update(item).subscribe(rse => {
+ resolve(item);
+ }, err => {
+ reject(item);
+ });
}
});
});
@Component({
selector: 'eg-staff-banner',
template: `
- <div class="lead alert alert-primary text-center pt-1 pb-1">
+ <div class="lead alert alert-primary text-center pt-1 pb-1"
+ [ngClass]="bannerStyle ? bannerStyle : 'alert-primary'">
<eg-title i18n-prefix [prefix]="bannerText"></eg-title>
- <span>{{bannerText}}</span>
+ <i class="material-icons align-middle text-left" *ngIf="bannerIcon">{{bannerIcon}}</i>
+ <span class="align-middle">{{bannerText}}</span>
</div>
`
})
export class StaffBannerComponent {
@Input() public bannerText: string;
+ @Input() public bannerIcon: string;
+ @Input() public bannerStyle: string;
}
$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.
+ /);
+
+__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_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;
+}
+
+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);
+ 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->{first_given_name} = $user->{patron_data}->first_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;
+
+ push @$targets, $final_user;
+ }
+
+ return $targets;
+
+}
__PACKAGE__->register_method(
method => 'fetch_copy_tags',
use OpenILS::WWW::EGCatLoader::Library;
use OpenILS::WWW::EGCatLoader::Search;
use OpenILS::WWW::EGCatLoader::Record;
+use OpenILS::WWW::EGCatLoader::Course;
use OpenILS::WWW::EGCatLoader::Container;
use OpenILS::WWW::EGCatLoader::SMS;
use OpenILS::WWW::EGCatLoader::Register;
return $self->load_record if $path =~ m|opac/record/\d|;
return $self->load_cnbrowse if $path =~ m|opac/cnbrowse|;
return $self->load_browse if $path =~ m|opac/browse|;
+ return $self->load_course if $path =~ m|opac/course|;
return $self->load_mylist_add if $path =~ m|opac/mylist/add|;
return $self->load_mylist_delete if $path =~ m|opac/mylist/delete|;
--- /dev/null
+package OpenILS::WWW::EGCatLoader;
+use strict; use warnings;
+use Apache2::Const -compile => qw(OK DECLINED FORBIDDEN HTTP_GONE HTTP_INTERNAL_SERVER_ERROR REDIRECT HTTP_BAD_REQUEST HTTP_NOT_FOUND);
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Application::AppUtils;
+use Net::HTTP::NB;
+use IO::Select;
+my $U = 'OpenILS::Application::AppUtils';
+
+sub load_course {
+ my $self = shift;
+ my $ctx = $self->ctx;
+
+ $ctx->{page} = 'course';
+ $ctx->{readonly} = $self->cgi->param('readonly');
+
+ my $course_id = $ctx->{page_args}->[0];
+
+ return Apache2::Const::HTTP_BAD_REQUEST
+ unless $course_id and $course_id =~ /^\d+$/;
+
+ $ctx->{course} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.courses.retrieve',
+ [$course_id]
+ )->[0];
+
+ $ctx->{instructors} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.course_users.retrieve',
+ $course_id
+ );
+
+ $ctx->{course_materials} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.course_materials.retrieve.fleshed',
+ {course => $course_id}
+ );
+ return Apache2::Const::OK;
+}
\ No newline at end of file
$ctx->{copies} = $copy_rec->gather(1);
+ my $course_module_opt_in = 0;
+ if ($ctx->{get_org_setting}->($org, "circ.course_materials_opt_in")) {
+ $course_module_opt_in = 1;
+ }
+
# Add public copy notes to each copy - and while we're in there, grab peer bib records
- # and copy tags
+ # and copy tags. Oh and if we're working with course materials, those too.
my %cached_bibs = ();
foreach my $copy (@{$ctx->{copies}}) {
$copy->{notes} = $U->simplereq(
'open-ils.circ.copy_note.retrieve.all',
{itemid => $copy->{id}, pub => 1 }
);
+ if ($course_module_opt_in) {
+ $copy->{course_materials} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.course_materials.retrieve',
+ {item => $copy->{id}}
+ );
+ my %course_ids;
+ for my $material (@{$copy->{course_materials}}) {
+ $course_ids{$material->course} = 1;
+ }
+
+ $copy->{courses} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.courses.retrieve',
+ keys %course_ids
+ );
+ }
$self->timelog("past copy note retrieval call");
my $meth = 'open-ils.circ.copy_tags.retrieve';
$meth .= ".staff" if $ctx->{is_staff};
}
}
+ my $course_module_opt_in = 0;
+ if ($ctx->{get_org_setting}->($self->_get_search_lib, "circ.course_materials_opt_in")) {
+ $course_module_opt_in = 1;
+ }
+
for my $rec (@{$ctx->{records}}) {
my ($res_rec) = grep { $_->[0] == $rec->{$id_key} } @{$results->{ids}};
$rec->{badges} = [split(',', $res_rec->[1])] if $res_rec->[1];
$rec->{popularity} = $res_rec->[2];
+ if ($course_module_opt_in) {
+ $rec->{course_materials} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.course_materials.retrieve',
+ {record => $rec->{id}}
+ );
+ my %course_ids;
+ for my $material (@{$rec->{course_materials}}) {
+ $course_ids{$material->course} = 1;
+ }
+
+ $rec->{courses} = $U->simplereq(
+ 'open-ils.circ',
+ 'open-ils.circ.courses.retrieve',
+ keys %course_ids
+ );
+ }
if ($tag_circs) {
# index 3 (5 for MR) in the per-record result array is a boolean which
# indicates whether the record in question is in the users
$rec->{user_circulated} = 1 if $res_rec->[$index];
}
}
+
$ctx->{search_facets} = $facets;
return ($cache_key, $list);
}
-1;
+1;
\ No newline at end of file
course INT NOT NULL REFERENCES asset.course_module_course (id),
item INT NOT NULL REFERENCES asset.copy (id),
relationship TEXT,
+ record INT REFERENCES biblio.record_entry (id),
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
+ original_callnumber INT REFERENCES asset.call_number,
+ unique (course, item)
);
CREATE TABLE asset.course_module_non_cat_course_materials (
course INT NOT NULL REFERENCES asset.course_module_course (id),
item INT NOT NULL REFERENCES asset.copy (id),
relationship TEXT,
+ record INT REFERENCES biblio.record_entry (id),
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
+ 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 (
--- /dev/null
+[%- PROCESS "opac/parts/header.tt2";
+ WRAPPER "opac/parts/base.tt2";
+ INCLUDE "opac/parts/topnav.tt2";
+ ctx.page_title = l("Course Details: [_1] - [_2]", ctx.course.name, ctx.course.course_number);
+-%]
+ [%- INCLUDE "opac/parts/searchbar.tt2" %]
+ <h2 class="sr-only">[% l('Course Details') %]</h2>
+ <br class="clear-both" />
+ <div id="content-wrapper" class="content-wrapper-record-page">
+ <div id="main-content">
+ [% INCLUDE "opac/parts/course/body.tt2" %]
+ <div class="common-full-pad"></div>
+ </div>
+ <br class="clear-both" />
+ </div>
+[%- END %]
padding-top: 1.5em;
}
-#rdetails_status td {
+#rdetails_status td,
+#course_material_table td {
[% IF rtl == 't' -%]
padding: 7px 13px 3px 0px;
[%- ELSE %]
white-space: normal;
}
-#rdetails_status thead th {
+#rdetails_status thead th,
+#course_material_table thead th {
[% IF rtl == 't' -%]
padding: 13px 13px 13px 0px;
text-align: right;
font-weight: bold;
}
-#rdetails_status tbody td {
+#rdetails_status tbody td,
+#course_material_table tbody td {
[% IF rtl == 't' -%]
padding-right: 13px;
text-align: right;
text-align: left;
[%- END %]
}
-#rdetails_status tbody td.copy_note {
+#rdetails_status tbody td.copy_note
+#course_material_table tbody {
color: [% css_colors.primary %];
text-wrap:normal;
white-space:pre-wrap !important;
vertical-align: top;
[% END -%]
}
-.rdetail-author-div {
+.rdetail-author-div, .course-instructor-div {
padding-bottom: 10px;
display: inline-block;
}
display: none;
}
-.rdetail_authors_div {
+.rdetail_authors_div, .course_instructors_div {
margin-bottom: 1em;
}
#main-content {
margin: 0 1px;
}
- #rdetails_status thead {
+ #rdetails_status thead,
+ #course_material_table thead {
display: none;
}
- #rdetails_status tr {
+ #rdetails_status tr,
+ #course_material_table tr {
display: block;
margin-top: 3px;
}
- #rdetails_status td {
+ #rdetails_status td,
+ #course_material_table td {
display: block;
padding: 1px;
}
.carousel .glide__arrow--left {
left: -5em;
}
+.archived_course {
+ color: [% css_colors.text_badnews %];
+ font-weight: bold;
+}
+.course_details {
+ padding-top: 1em;
+}
+.course_details_div {
+ padding-bottom: 1em;
+}
\ No newline at end of file
--- /dev/null
+<div id='canvas_main' class='canvas'>
+ <div class="course_details">
+ <div>
+ <h1>
+ [% l(ctx.course.name) %] ([% ctx.course.course_number %])
+ [% IF ctx.course.is_archived == 't' %]
+ <span class="archived_course">
+ This course is inactive.
+ </span>
+ [% END %]
+ </h1>
+ [% IF ctx.instructors.size %]
+ <div class="course_instructors_div">
+ <strong>Course Instructors:</strong>
+ [%- FOR instructor IN ctx.instructors;
+ instructorString = '';
+ IF instructor.pref_family_name;
+ instructorString = instructorString _ instructor.pref_family_name _ ', ';
+ ELSE;
+ instructorString = instructorString _ instructor.family_name _ ', ';
+ END;
+ IF instructor.pref_first_given_name;
+ instructorString = instructorString _ instructor.pref_first_given_name;
+ ELSE;
+ instructorString = instructorString _ instructor.first_given_name;
+ END;
+ instructorString = instructorString _ ' (' _ l(instructor.usr_role) _ ')'; %]
+ <span class="course-instructor-div">[% instructorString %].</span>
+ [% END %]
+ </div>
+ [% END %]
+
+ <div class="course_details_div">
+ <h2>[% l('Course Details') %]</h2>
+ <div>
+ <span><strong>[% l('Course Title') %]: </strong></span>
+ <span>[% ctx.course.name %]</span>
+ </div>
+ <div>
+ <span><strong>[% l('Course Number') %]: </strong></span>
+ <span>[% ctx.course.course_number %]</span>
+ <div>
+ </div>
+ <span><strong>[% l('Section Number') %]: </strong></span>
+ <span>[% ctx.course.section_number %]</span>
+ </div>
+ <div>
+ [%
+ owning_lib = ctx.get_aou(ctx.course.owning_lib);
+ lib_url = ctx.get_org_setting(owning_lib.id, 'lib.info_url');
+ prefer_external_url = ctx.get_org_setting(owning_lib.id, 'lib.prefer_external_url');
+ UNLESS lib_url && prefer_external_url;
+ lib_url = mkurl(ctx.opac_root _ '/library/' _ owning_lib.shortname, {}, 1);
+ END;
+ %]
+ <span><strong>[% l('Owning Library') %]: </strong></span>
+ <span><a href="[% lib_url %]">[% owning_lib.name %]</a></span>
+ </div>
+ </div>
+
+ [%- UNLESS ctx.course.is_archived == 't' %]
+ <div>
+ <span><h2>[% l('Course Materials') %]</h2></span>
+ <table class="table_no_border_space table_no_cell_pad table_no_border" width="100%" id="course_material_table">
+ <thead>
+ <tr>
+ <th scope="col">Location</th>
+ <th scope="col">Call Number</th>
+ <th scope="col">Title</th>
+ <th scope="col">Barcode</th>
+ <th scope="col">Relationship</th>
+ <th scope="col">Status</th>
+ <th scope="col">Shelving Location</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH copy_info 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>
+ <td>[% copy_info.call_number %]</td>
+ <td>
+ <a href="[% mkurl(ctx.opac_root _ '/record/' _ copy_info.record) %]">
+ [% copy_info.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>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ </div>
+ [% END %]
+ </div>
+ </div>
+</div>
[%- END %]
<th scope='col'>[% l("Status") %]</th>
<th scope='col'>[% l("Due Date") %]</th>
+ [%- IF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
+ <th scope='col'>[% l("Courses") %]</th>
+ [%- END %]
</tr>
</thead>
<tbody class="copy_details_table">
<td>[% bib.target_copy.location.name | html %]</td>
<td>[% bib.target_copy.status.name | html %]</td>
<td>[% date.format(ctx.parse_datetime(copy_info.due_date, copy_info.circ_circ_lib),DATE_FORMAT) %]</td>
+ [%- IF ctx.get_org_setting(CGI.param('loc')
+ OR ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
+ <td></td>
+ [%- END %]
</tr>
[%- END; # FOREACH peer
END; # FOREACH bib
ELSE;
'-';
END %]</td>
+ [%- IF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
+ <td>[%- FOREACH course IN copy_info.courses %]
+ <div>[% course.course_number %]</div>
+ [% END %]</td>
+ [% END %]
</tr>
[% IF copy_info.notes; %]
[% UNLESS rec.mmr_id;
PROCESS "opac/parts/result/copy_counts.tt2";
END; %]
+ [%- IF ctx.get_org_setting(ctx.search_ou, 'circ.course_materials_opt_in') == 1 %]
+ [%- courseStrings = [] %]
+ [% FOREACH course IN rec.courses %]
+ [% courseString = course.name _ ' (' _ course.course_number _ ')' %]
+ [% courseStrings.push(courseString); %]
+ [% END %]
+ [% IF courseStrings.size > 0 %]
+ <span><strong>[% l('Associated Courses: ') %]</strong></span>
+ <span>[% courseStrings.join(', ') %]</span>
+ [% END %]
+ [% END %]
[% IF rec.user_circulated %]
<div class="result_item_circulated">
<img src="[% ctx.media_prefix %]/images/green_check.png[% ctx.cache_key %]" alt="[% l('Checked Out Before') %]"/>