1 import { PermService } from '@eg/core/perm.service';
2 import {Component, Input, ViewChild, OnInit} from '@angular/core';
3 import { Observable, merge, of, EMPTY, throwError, from } from 'rxjs';
4 import { switchMap, concatMap } from 'rxjs/operators';
5 import {DialogComponent} from '@eg/share/dialog/dialog.component';
6 import {AuthService} from '@eg/core/auth.service';
7 import {NetService} from '@eg/core/net.service';
8 import {PcrudService} from '@eg/core/pcrud.service';
9 import {Pager} from '@eg/share/util/pager';
10 import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
11 import {GridDataSource} from '@eg/share/grid/grid';
12 import {GridComponent} from '@eg/share/grid/grid.component';
13 import {IdlObject} from '@eg/core/idl.service';
14 import {StringComponent} from '@eg/share/string/string.component';
15 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
16 import {ToastService} from '@eg/share/toast/toast.service';
17 import {CourseService} from '@eg/staff/share/course.service';
20 selector: 'eg-course-associate-material-dialog',
21 templateUrl: './course-associate-material.component.html'
24 export class CourseAssociateMaterialComponent extends DialogComponent implements OnInit {
25 @Input() currentCourse: IdlObject;
26 @Input() courseId: any;
27 @Input() courseIsArchived: string;
28 @Input() displayMode: string;
29 materials: any[] = [];
30 @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
31 @ViewChild('materialsGrid', {static: false}) materialsGrid: GridComponent;
32 @ViewChild('materialDeleteFailedString', { static: true })
33 materialDeleteFailedString: StringComponent;
34 @ViewChild('materialDeleteSuccessString', { static: true })
35 materialDeleteSuccessString: StringComponent;
36 @ViewChild('materialAddSuccessString', { static: true })
37 materialAddSuccessString: StringComponent;
38 @ViewChild('materialAddFailedString', { static: true })
39 materialAddFailedString: StringComponent;
40 @ViewChild('materialEditSuccessString', { static: true })
41 materialEditSuccessString: StringComponent;
42 @ViewChild('materialEditFailedString', { static: true })
43 materialEditFailedString: StringComponent;
44 @ViewChild('materialAddDifferentLibraryString', { static: true })
45 materialAddDifferentLibraryString: StringComponent;
46 @ViewChild('confirmOtherLibraryDialog') confirmOtherLibraryDialog: DialogComponent;
47 @ViewChild('otherLibraryNoPermissionsAlert') otherLibraryNoPermissionsAlert: DialogComponent;
48 materialsDataSource: GridDataSource;
49 @Input() barcodeInput: string;
50 @Input() relationshipInput: string;
51 @Input() tempCallNumber: string;
52 @Input() tempStatus: number;
53 @Input() tempLocation: number;
54 @Input() tempCircMod: string;
55 @Input() isModifyingStatus: boolean;
56 @Input() isModifyingCircMod: boolean;
57 @Input() isModifyingCallNumber: boolean;
58 @Input() isModifyingLocation: boolean;
59 isModifyingLibrary: boolean;
63 associateBriefRecord: (newRecord: string) => void;
64 associateElectronicBibRecord: () => void;
67 private auth: AuthService,
68 private course: CourseService,
69 private net: NetService,
70 private pcrud: PcrudService,
71 private toast: ToastService,
72 private perm: PermService,
73 private modal: NgbModal
76 this.materialsDataSource = new GridDataSource();
78 this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
79 return this.net.request(
81 'open-ils.courses.course_materials.retrieve.fleshed',
82 {course: this.courseId}
88 this.associateBriefRecord = (newRecord: string) => {
89 return this.net.request(
91 'open-ils.courses.attach.biblio_record',
95 this.relationshipInput
97 this.materialsGrid.reload();
98 this.materialAddSuccessString.current()
99 .then(str => this.toast.success(str));
103 this.associateElectronicBibRecord = () => {
104 return this.net.request(
106 'open-ils.courses.attach.electronic_resource',
110 this.relationshipInput
112 this.materialsGrid.reload();
113 this.materialAddSuccessString.current()
114 .then(str => this.toast.success(str));
120 isDialog(): boolean {
121 return this.displayMode === 'dialog';
124 editSelectedMaterials(itemFields: IdlObject[]) {
125 // Edit each IDL thing one at a time
126 const editOneThing = (item: IdlObject) => {
127 if (!item) { return; }
129 this.showEditDialog(item).then(
130 () => editOneThing(itemFields.shift()));
133 editOneThing(itemFields.shift());
136 showEditDialog(courseMaterial: IdlObject): Promise<any> {
137 this.editDialog.mode = 'update';
138 this.editDialog.recordId = courseMaterial.id();
139 return new Promise((resolve, reject) => {
140 this.editDialog.open({size: 'lg'}).subscribe(
142 this.materialEditSuccessString.current()
143 .then(str => this.toast.success(str));
144 // eslint-disable-next-line rxjs/no-nested-subscribe
145 this.pcrud.retrieve('acmcm', result).subscribe(material => {
146 if (material.course() !== this.courseId) {
147 this.materialsGrid.reload();
149 courseMaterial.relationship = material.relationship();
154 (error: unknown) => {
155 this.materialEditFailedString.current()
156 .then(str => this.toast.danger(str));
163 associateItem(barcode, relationship) {
164 if (!barcode || barcode.length === 0) { return; }
165 this.barcodeInput = null;
167 this.pcrud.search('acp', {barcode: barcode.trim()}, {
168 flesh: 3, flesh_fields: {acp: ['call_number', 'circ_lib']}
169 }).pipe(switchMap(item => {
170 this.isModifyingLibrary = item.circ_lib().id() !== this.currentCourse.owning_lib().id();
171 return this.isModifyingLibrary ? this.handleItemAtDifferentLibrary$(item) : of(item);
173 .subscribe((originalItem) => {
175 barcode: barcode.trim(),
176 relationship: relationship,
177 isModifyingCallNumber: this.isModifyingCallNumber,
178 isModifyingCircMod: this.isModifyingCircMod,
179 isModifyingLocation: this.isModifyingLocation,
180 isModifyingStatus: this.isModifyingStatus,
181 isModifyingLibrary: this.isModifyingLibrary,
182 tempCircMod: this.tempCircMod,
183 tempLocation: this.tempLocation,
184 tempLibrary: this.currentCourse.owning_lib().id(),
185 tempStatus: this.tempStatus,
186 currentCourse: this.currentCourse
189 const associatedMaterial = this.course.associateMaterials(originalItem, args);
191 associatedMaterial.material.then(res => {
192 const item = associatedMaterial.item;
193 let new_cn = item.call_number().label();
194 if (this.tempCallNumber) { new_cn = this.tempCallNumber; }
195 this.course.updateItem(item, this.currentCourse.owning_lib(),
196 new_cn, args.isModifyingCallNumber
198 this.materialsGrid.reload();
199 this.materialAddSuccessString.current()
200 .then(str => this.toast.success(str));
203 this.materialAddFailedString.current()
204 .then(str => this.toast.danger(str));
209 deleteSelectedMaterials(items) {
210 const deleteRequest$ = this.course.detachMaterials(items);
211 merge(...deleteRequest$).subscribe(
213 this.materialDeleteSuccessString.current().then(str => this.toast.success(str));
216 this.materialDeleteFailedString.current()
217 .then(str => this.toast.danger(str));
220 this.materialsGrid.reload();
224 private handleItemAtDifferentLibrary$(item: IdlObject): Observable<any> {
225 this.itemCircLib = item.circ_lib().shortname();
226 const promise = this.perm.hasWorkPermAt(['UPDATE_COPY'], true).then(result => {
227 return result.UPDATE_COPY as number[];
229 return from(promise).pipe(concatMap((editableItemLibs) => {
230 if (editableItemLibs.indexOf(item.circ_lib().id()) !== -1) {
231 return this.confirmOtherLibraryDialog.open()
232 .pipe(switchMap(confirmed => {
233 // If the user clicked "no", return an empty observable,
234 // so the subsequent code has nothing to do.
235 if (!confirmed) { return EMPTY; }
239 return this.otherLibraryNoPermissionsAlert.open()
240 .pipe(switchMap(() => EMPTY));