From 44b153ff0a6e0dfe33f346f6c22a3c767daa027e Mon Sep 17 00:00:00 2001 From: Kyle Huckins Date: Mon, 25 Nov 2019 22:06:04 +0000 Subject: [PATCH] lp1849212 Angular Catalog Course integration - Add a column retrieving the names of courses linked to materials when opted into the Course Reserves functionality. - Expand the bib record summary when opted in to display all courses associated with an item. - Display associated courses on Search Results UI - Move bulk of Associate Item funcitonality into Course Service Signed-off-by: Kyle Huckins Signed-off-by: Jane Sandberg Signed-off-by: Michele Morgan Signed-off-by: Galen Charlton --- .../course-associate-material.component.ts | 67 +++++-------- .../course-reserves/course-reserves.module.ts | 4 +- .../catalog/record/copies.component.html | 7 ++ .../staff/catalog/record/copies.component.ts | 13 +++ .../catalog/result/record.component.html | 4 + .../staff/catalog/result/record.component.ts | 24 ++++- .../src/eg2/src/app/staff/common.module.ts | 2 + .../bib-summary/bib-summary.component.html | 19 ++++ .../bib-summary/bib-summary.component.ts | 22 ++++- .../eg2/src/app/staff/share/course.service.ts | 98 +++++++++++++++++++ 10 files changed, 215 insertions(+), 45 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.ts index 5fcad15b96..a6c1971e6e 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.ts @@ -88,53 +88,38 @@ export class CourseAssociateMaterialComponent extends DialogComponent { associateItem(barcode, relationship) { if (barcode) { - this.pcrud.search('acp', {barcode: barcode}, - {flesh: 3, flesh_fields: {acp: ['call_number']}}).subscribe(item => { - let material = this.idl.create('acmcm'); - material.item(item.id()); - material.course(this.currentCourse.id()); - if (relationship) material.relationship(relationship); - if (this.isModifyingStatus && this.tempStatus) { - material.original_status(item.status()); - item.status(this.tempStatus); - } - if (this.isModifyingLocation && this.tempLocation) { - material.original_location(item.location()); - item.location(this.tempLocation); - } - if (this.isModifyingCircMod) { - material.original_circ_modifier(item.circ_modifier()); - item.circ_modifier(this.tempCircMod); - if (!this.tempCircMod) item.circ_modifier(null); - } - if (this.isModifyingCallNumber) { - material.original_callnumber(item.call_number()); - } - this.pcrud.create(material).subscribe( - val => { - console.debug('created: ' + val); - let new_cn = item.call_number().label(); - if (this.tempCallNumber) new_cn = this.tempCallNumber; - this.courseSvc.updateItem(item, this.currentCourse.owning_lib(), new_cn, this.isModifyingCallNumber).then(res => { - this.fetchItem(item.id(), relationship); + 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.pcrud.search('acp', {barcode: 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(); + if (this.tempCallNumber) new_cn = this.tempCallNumber; + this.courseSvc.updateItem(item, + this.currentCourse.owning_lib(), + new_cn, args.isModifyingCallNumber).then(resp => { + this.fetchItem(item.id(), args.relationship); if (item.circ_lib() != this.currentCourse.owning_lib()) { this.differentLibraryString.current().then(str => this.toast.warning(str)); } else { this.successString.current().then(str => this.toast.success(str)); } }); - - // Cleaning up inputs - this.barcodeInput = ""; - this.relationshipInput = ""; - this.tempStatus = null; - this.tempCircMod = null; - this.tempCallNumber = null; - this.tempLocation = null; - this.isModifyingCallNumber = false; - this.isModifyingCircMod = false; - this.isModifyingLocation = false; - this.isModifyingStatus = false; }, err => { this.failedString.current().then(str => this.toast.danger(str)); }); diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts index 64bd5dc183..0ca63cfab6 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts @@ -1,11 +1,11 @@ import {NgModule} from '@angular/core'; import {TreeModule} from '@eg/share/tree/tree.module'; +import {StaffCommonModule} from '@eg/staff/common.module'; import {AdminCommonModule} from '@eg/staff/admin/common.module'; import {CourseListComponent} from './course-list.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'; -import {CourseService} from '@eg/staff/share/course.service'; @NgModule({ declarations: [ @@ -13,6 +13,7 @@ import {CourseService} from '@eg/staff/share/course.service'; CourseAssociateMaterialComponent ], imports: [ + StaffCommonModule, AdminCommonModule, CourseReservesRoutingModule, ItemLocationSelectModule, @@ -21,7 +22,6 @@ import {CourseService} from '@eg/staff/share/course.service'; exports: [ ], providers: [ - CourseService ] }) diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html index 8c511c3e6d..ab44c6cb33 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html @@ -35,6 +35,10 @@ No + +
{{course.course_number()}}
+
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts index 9e288ee61d..b9f2ec68a6 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts @@ -8,6 +8,7 @@ import {OrgService} from '@eg/core/org.service'; import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid'; import {GridComponent} from '@eg/share/grid/grid.component'; import {BroadcastService} from '@eg/share/util/broadcast.service'; +import {CourseService} from '@eg/staff/share/course.service'; @Component({ selector: 'eg-catalog-copies', @@ -17,6 +18,7 @@ export class CopiesComponent implements OnInit { recId: number; initDone = false; + usingCourseModule = false; gridDataSource: GridDataSource; copyContext: any; // grid context @ViewChild('copyGrid', { static: true }) copyGrid: GridComponent; @@ -33,6 +35,7 @@ export class CopiesComponent implements OnInit { cellTextGenerator: GridCellTextGenerator; constructor( + private course: CourseService, private net: NetService, private org: OrgService, private staffCat: StaffCatalogService, @@ -43,6 +46,9 @@ export class CopiesComponent implements OnInit { ngOnInit() { this.initDone = true; + this.course.isOptedIn().then(res => { + this.usingCourseModule = res; + }); this.gridDataSource.getRows = (pager: Pager, sort: any[]) => { // sorting not currently supported @@ -94,6 +100,13 @@ export class CopiesComponent implements OnInit { pager.offset, this.staffCat.prefOrg ? this.staffCat.prefOrg.id() : null ).pipe(map(copy => { + this.org.settings('circ.course_materials_opt_in').then(res => { + if (res['circ.course_materials_opt_in']) { + this.course.getCoursesFromMaterial(copy.id).then(courseList => { + copy._courses = courseList; + }); + } + }); copy.active_date = copy.active_date || copy.create_date; return copy; })); diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html index 8d839e19b2..85d75a215a 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html @@ -125,6 +125,10 @@ field="issn" joiner=",">
+ +
Associated Courses: + {{courseNames.join(', ')}}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts index 664d36661e..94204c0ee8 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts @@ -10,6 +10,7 @@ import {CatalogSearchContext} from '@eg/share/catalog/search-context'; import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service'; import {StaffCatalogService} from '../catalog.service'; import {BasketService} from '@eg/share/catalog/basket.service'; +import {CourseService} from '@eg/staff/share/course.service'; @Component({ selector: 'eg-catalog-result-record', @@ -29,6 +30,8 @@ export class ResultRecordComponent implements OnInit, OnDestroy { searchContext: CatalogSearchContext; isRecordSelected: boolean; basketSub: Subscription; + has_course: boolean; + courseNames: any[] = []; constructor( private router: Router, @@ -38,11 +41,13 @@ export class ResultRecordComponent implements OnInit, OnDestroy { private cat: CatalogService, private catUrl: CatalogUrlService, private staffCat: StaffCatalogService, - private basket: BasketService + private basket: BasketService, + private course: CourseService ) {} ngOnInit() { this.searchContext = this.staffCat.searchContext; + this.loadCourseInformation(this.summary.id) this.isRecordSelected = this.basket.hasRecordId(this.summary.id); // Watch for basket changes caused by other components @@ -55,6 +60,23 @@ export class ResultRecordComponent implements OnInit, OnDestroy { this.basketSub.unsubscribe(); } + loadCourseInformation(recordId) { + console.log("Entering loadCourseInformation"); + this.course.isOptedIn().then(res => { + 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.has_course = true; + }); + } else { + this.has_course = false; + } + }); + } + orgName(orgId: number): string { return this.org.get(orgId).shortname(); } diff --git a/Open-ILS/src/eg2/src/app/staff/common.module.ts b/Open-ILS/src/eg2/src/app/staff/common.module.ts index a2f54cf7ba..62c921c829 100644 --- a/Open-ILS/src/eg2/src/app/staff/common.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/common.module.ts @@ -18,6 +18,7 @@ import {MultiSelectComponent} from '@eg/share/multi-select/multi-select.componen import {NotBeforeMomentValidatorDirective} from '@eg/share/validators/not_before_moment_validator.directive'; import {PatronBarcodeValidatorDirective} from '@eg/share/validators/patron_barcode_validator.directive'; import {BroadcastService} from '@eg/share/util/broadcast.service'; +import {CourseService} from './share/course.service' /** * Imports the EG common modules and adds modules common to all staff UI's. @@ -72,6 +73,7 @@ export class StaffCommonModule { AccessKeyService, AudioService, BroadcastService + CourseService ] }; } diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html index 3fd955888f..0dea4ba486 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html @@ -91,6 +91,25 @@
{{summary.record.edit_date() | date:'short'}}
+ +
  • +
    +
    Associated Courses
    +
    +
  • +
  • +
    +
    Course Name:
    +
    {{course.name()}}
    +
    Course Number:
    +
    {{course.course_number()}}
    +
    Section Number:
    +
    {{course.section_number()}}
    +
    Owning Library:
    +
    {{this.org.get(course.owning_lib()).shortname()}}
    +
    +
  • +
    diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts index 84719fd3d5..a227c078a3 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts @@ -1,5 +1,6 @@ import {Component, OnInit, Input} from '@angular/core'; import {OrgService} from '@eg/core/org.service'; +import {CourseService} from '@eg/staff/share/course.service'; import {BibRecordService, BibRecordSummary } from '@eg/share/catalog/bib-record.service'; import {ServerStoreService} from '@eg/core/server-store.service'; @@ -13,6 +14,8 @@ import {CatalogService} from '@eg/share/catalog/catalog.service'; export class BibSummaryComponent implements OnInit { initDone = false; + has_course = false; + courses: any; // True / false if the display is vertically expanded private _exp: boolean; @@ -33,6 +36,7 @@ export class BibSummaryComponent implements OnInit { this.summary = s; if (this.initDone && this.summary) { this.summary.getBibCallNumber(); + this.loadCourseInformation(this.summary.record.id()); } } @@ -40,13 +44,15 @@ export class BibSummaryComponent implements OnInit { private bib: BibRecordService, private org: OrgService, private store: ServerStoreService, - private cat: CatalogService + private cat: CatalogService, + private course: CourseService ) {} ngOnInit() { if (this.summary) { this.summary.getBibCallNumber(); + this.loadCourseInformation(this.summary.record.id()); } else { if (this.recordId) { this.loadSummary(); @@ -63,6 +69,7 @@ export class BibSummaryComponent implements OnInit { } loadSummary(): void { + this.loadCourseInformation(this.recordId); this.bib.getBibSummary(this.recordId).toPromise() .then(summary => { summary.getBibCallNumber(); @@ -70,6 +77,19 @@ export class BibSummaryComponent implements OnInit { }); } + loadCourseInformation(record_id) { + this.org.settings('circ.course_materials_opt_in').then(setting => { + if (setting['circ.course_materials_opt_in']) { + this.course.fetchCopiesInCourseFromRecord(record_id).then(course_list => { + this.courses = course_list; + this.has_course = true; + }); + } else { + this.has_course = false; + } + }); + } + orgName(orgId: number): string { if (orgId) { return this.org.get(orgId).shortname(); diff --git a/Open-ILS/src/eg2/src/app/staff/share/course.service.ts b/Open-ILS/src/eg2/src/app/staff/share/course.service.ts index c8b60cc15d..0856cfd6c1 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/course.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/course.service.ts @@ -1,9 +1,12 @@ +import {Injectable} from '@angular/core'; import {AuthService} from '@eg/core/auth.service'; import {EventService} from '@eg/core/event.service'; import {IdlObject, IdlService} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; +import {OrgService} from '@eg/core/org.service'; import {PcrudService} from '@eg/core/pcrud.service'; +@Injectable() export class CourseService { constructor( @@ -11,9 +14,104 @@ export class CourseService { private evt: EventService, private idl: IdlService, private net: NetService, + private org: OrgService, private pcrud: PcrudService ) {} + isOptedIn(): Promise { + return new Promise((resolve, reject) => { + this.org.settings('circ.course_materials_opt_in').then(res => { + resolve(res['circ.course_materials_opt_in']); + }); + }); + } + getCourses(course_ids?: Number[]): Promise { + if (!course_ids) { + return this.pcrud.retrieveAll('acmc', + {}, {atomic: true}).toPromise(); + } else { + return this.pcrud.search('acmc', {id: course_ids}, + {}, {atomic: true}).toPromise(); + } + } + + getCoursesFromMaterial(copy_id): Promise { + let id_list = []; + return new Promise((resolve, reject) => { + + return this.pcrud.search('acmcm', {item: copy_id}) + .subscribe(materials => { + if (materials) { + id_list.push(materials.course()); + } + }, err => { + console.log(err); + reject(err); + }, () => { + if (id_list.length) { + return this.getCourses(id_list).then(courses => { + resolve(courses); + }); + + } + }); + }); + } + + fetchCopiesInCourseFromRecord(record_id) { + let cp_list = []; + let course_list = []; + return new Promise((resolve, reject) => { + this.net.request( + 'open-ils.cat', + 'open-ils.cat.asset.copy_tree.global.retrieve', + this.auth.token(), record_id + ).subscribe(copy_tree => { + copy_tree.forEach(cn => { + cn.copies().forEach(cp => { + cp_list.push(cp.id()); + }); + }); + }, err => reject(err), + () => { + resolve(this.getCoursesFromMaterial(cp_list)); + }); + }); + } + + // Creating a new acmcm Entry + associateMaterials(item, args) { + console.log("entering associateMaterials") + let material = this.idl.create('acmcm'); + material.item(item.id()); + material.course(args.currentCourse.id()); + if (args.relationship) material.relationship(args.relationship); + + // Apply temporary fields to the item + if (args.isModifyingStatus && args.tempStatus) { + material.original_status(item.status()); + item.status(args.tempStatus); + } + if (args.isModifyingLocation && args.tempLocation) { + material.original_location(item.location()); + item.location(args.tempLocation); + } + if (args.isModifyingCircMod) { + material.original_circ_modifier(item.circ_modifier()); + item.circ_modifier(args.tempCircMod); + if (!args.tempCircMod) item.circ_modifier(null); + } + if (args.isModifyingCallNumber) { + material.original_callnumber(item.call_number()); + } + let response = { + item: item, + material: this.pcrud.create(material).toPromise() + }; + + return response; + } + disassociateMaterials(courses) { return new Promise((resolve, reject) => { let course_ids = []; -- 2.43.2