<class id="acmc" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_course" oils_persist:tablename="asset.course_module_course" reporter:label="Course">
<fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_course_id_seq">
<field reporter:label="ID" name="id" reporter:datatype="id" />
- <field reporter:label="Title" name="name" reporter:datatype="text" />
- <field reporter:label="Course Number" name="course_number" reporter:datatype="text" />
+ <field reporter:label="Course Name" name="name" reporter:datatype="text" oils_obj:required="true" />
+ <field reporter:label="Course Number" name="course_number" reporter:datatype="text" oils_obj:required="true" />
<field reporter:label="Section Number" name="section_number" reporter:datatype="text" />
- <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="link" />
+ <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="link" oils_obj:required="true" />
<field reporter:label="Course Members" name="members" oils_persist:virtual="true" reporter:datatype="link" />
<field reporter:label="Course Materials" name="materials" oils_persist:virtual="true" reporter:datatype="link" />
- <field reporter:label="Non-Cataloged Course Materials" name="non_cat_materials" oils_persist:virtual="true" reporter:datatype="link" />
<field reporter:label="Is Archived?" name="is_archived" reporter:datatype="bool" />
</fields>
<links>
placeholder="e.g. Required" 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">
- <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-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>
<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>
+ <label for="bib-id" class="input-group-text" i18n>Bibliographic Record ID</label>
</div>
- <input type="text" [(ngModel)]="relationshipInput"
+ <input type="text" [(ngModel)]="bibId" id="bib-id"
[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>
+ <span class="input-group-text" i18n>Relationship</span>
</div>
- <input type="text" [(ngModel)]="bibId" id="bib-id"
+ <input type="text" [(ngModel)]="relationshipInput"
[disabled]="currentCourse && currentCourse.is_archived() == 't'" class="flex-grow-1" />
</div>
</div>
</ngb-tabset>
<div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
- <eg-grid #materialsGrid [dataSource]="materialsDataSource">
+ <eg-grid #materialsGrid [dataSource]="materialsDataSource" [useLocalSort]="true">
<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 label="Barcode" i18n-label name="barcode" [cellTemplate]="barcodeCellTemplate"></eg-grid-column>
+ <eg-grid-column label="Title" i18n-label name="title" flex="3" [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>
import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
-import {from, Observable} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
+import {from, merge, Observable} from 'rxjs';
import {DialogComponent} from '@eg/share/dialog/dialog.component';
import {AuthService} from '@eg/core/auth.service';
import {NetService} from '@eg/core/net.service';
@Input() displayMode: String;
materials: any[] = [];
@ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
- @ViewChild('materialsGrid', {static: true}) materialsGrid: GridComponent;
+ @ViewChild('materialsGrid', {static: false}) materialsGrid: GridComponent;
@ViewChild('materialDeleteFailedString', { static: true })
materialDeleteFailedString: StringComponent;
@ViewChild('materialDeleteSuccessString', { static: true })
}
deleteSelectedMaterials(items) {
- const item_ids = [];
+ const deleteRequest$ = [];
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));
- }
- );
+ deleteRequest$.push(this.net.request(
+ 'open-ils.courses', 'open-ils.courses.detach_material',
+ this.auth.token(), item.id()));
});
+ merge(...deleteRequest$).subscribe(
+ val => {
+ this.materialDeleteSuccessString.current().then(str => this.toast.success(str));
+ },
+ err => {
+ this.materialDeleteFailedString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
}
}
}
]);
return $e->event unless (($bib->source && $bib->source->transcendant) || $located_uris);
- _attach_bib($e, $course, $record, $relationship);
+ _attach_bib($e, $course, $record, $relationship, 0);
return 1;
}
->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);
+ _attach_bib($e, $course, $bib_create->id, $relationship, 1) if ($bib_create);
return 1;
}
# Shared logic for both e-resources and brief bibs
sub _attach_bib {
- my ($e, $course, $record, $relationship) = @_;
+ my ($e, $course, $record, $relationship, $temporary) = @_;
my $acmcm = Fieldmapper::asset::course_module_course_materials->new;
$acmcm->course($course);
$acmcm->record($record);
$acmcm->relationship($relationship);
+ $acmcm->temporary_record($temporary);
$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,
}
+__PACKAGE__->register_method(
+ method => 'detach_material',
+ api_name => 'open-ils.courses.detach_material',
+ signature => {
+ desc => 'Detaches a material from a course',
+ params => [
+ {desc => 'Authentication token', type => 'string'},
+ {desc => 'Course material id', type => 'number'},
+ ],
+ return => {desc => '1 on success, event on failure'}
+ });
+sub detach_material {
+ my ($self, $conn, $authtoken, $acmcm_id) = @_;
+ 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 $acmcm = $e->retrieve_asset_course_module_course_materials($acmcm_id)
+ or return $e->die_event;
+ my $bre_id_to_delete = $acmcm->temporary_record ? $acmcm->record : 0;
+ if ($bre_id_to_delete) {
+ # delete any attached located URIs
+ my $located_uri_cn_ids = $e->search_asset_call_number(
+ {record=>$bre_id_to_delete}, {idlist=>1});
+
+ for my $cn_id (@$located_uri_cn_ids) {
+ $e->delete_asset_call_number(
+ $e->retrieve_asset_call_number($cn_id))
+ or return $e->die_event;
+ }
+ OpenSRF::AppSession
+ ->create('open-ils.cat')
+ ->request('open-ils.cat.biblio.record_entry.delete',
+ $authtoken, $bre_id_to_delete);
+ }
+ if ($acmcm->item) {
+ _resetItemFields($e, $authtoken, $acmcm);
+ }
+
+ $e->delete_asset_course_module_course_materials($acmcm) or return $e->die_event;
+ $e->commit;
+ return 1;
+}
+
+sub _resetItemFields {
+ my ($e, $authtoken, $acmcm) = @_;
+ my $cat_sess = OpenSRF::AppSession->connect('open-ils.cat');
+ my $acp = $e->retrieve_asset_copy($acmcm->item);
+ my $course_lib = $e->retrieve_asset_course_module_course($acmcm->course)->owning_lib;
+ if ($acmcm->original_status) {
+ $acp->status($acmcm->orginal_status);
+ }
+ if ($acmcm->original_circ_modifier) {
+ $acp->status($acmcm->orginal_circ_modifier);
+ }
+ if ($acmcm->original_location) {
+ $acp->status($acmcm->orginal_location);
+ }
+ $e->update_asset_copy($acmcm);
+ if ($acmcm->original_callnumber) {
+ my $existing_acn = $e->retrieve_asset_call_number($acp->call_number);
+ # Let's attach to an existing call number, if one exists with the original label
+ # and other appropriate specifications
+ my $acn_id = cat_sess->request('open-ils.cat.call_number.find_or_create',
+ $authtoken, $acmcm->original_callnumber,
+ $existing_acn->record, $course_lib,
+ $existing_acn->prefix, $existing_acn->suffix,
+ $existing_acn->label_class)->acn_id;
+ cat_sess->request('open-ils.cat.transfer_copies_to_volume',
+ $authtoken, $acn_id, [$acp->id]);
+ }
+}
+
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)
--- /dev/null
+#!perl
+
+use strict; use warnings;
+use Test::More tests => 3;
+use OpenILS::Utils::TestUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Application::AppUtils;
+
+diag("Test the course materials module.");
+
+my $script = OpenILS::Utils::TestUtils->new();
+our $apputils = 'OpenILS::Application::AppUtils';
+
+# we need auth to access protected APIs
+$script->authenticate({
+ username => 'admin',
+ password => 'demo123',
+ type => 'staff'});
+
+my $authtoken = $script->authtoken;
+ok($authtoken, 'Have an authtoken');
+
+my $e = new_editor(xact => 1);
+$e->init;
+
+
+# -----------------------------------------------------------------------------
+# 1. Let's attach an existing biblio record entry to course #1, then delete it
+# -----------------------------------------------------------------------------
+
+my $acmcm = Fieldmapper::asset::course_module_course_materials->new;
+$acmcm->course(1);
+$acmcm->id(9999);
+$acmcm->record(55);
+$acmcm->temporary_record(0);
+$e->create_asset_course_module_course_materials( $acmcm ); # associated this bib record with a course
+$e->commit;
+
+$apputils->simplereq('open-ils.courses', 'open-ils.courses.detach.material', $authtoken, 9999);
+
+my $results = $e->search_asset_course_module_course_materials({id => 9999});
+is(scalar(@$results), 0, 'Successfully deleted acmcm');
+
+$results = $e->search_biblio_record_entry({id => 55, deleted => 0});
+
+is(scalar(@$results), 1,
+ 'Did not inadvertantly delete bre');
+
+
+# -----------------------------------------------------------------------------
+# 2. Let's create a brief temporary bib record, attach it to course #1, then detach it
+# -----------------------------------------------------------------------------
+
+my $temp_tcn_source = 'temporary bib record for course materials module test';
+
+$e->xact_begin;
+my $bre = Fieldmapper::biblio::record_entry->new;
+$bre->marc('<record></record>');
+$bre->tcn_source($temp_tcn_source); #Use the tcn_source field, since Cstore rewrites the last_xact_id field
+$e->create_biblio_record_entry($bre);
+$e->commit;
+
+my $bib_id = $e->search_biblio_record_entry({tcn_source=>$temp_tcn_source}, {idlist=>1})->[0];
+
+$e->xact_begin;
+$acmcm = Fieldmapper::asset::course_module_course_materials->new;
+$acmcm->course(1);
+$acmcm->id(9998);
+$acmcm->record($bib_id);
+$acmcm->temporary_record(1); # this one is temporary, like brief records created in the course module interface
+$e->create_asset_course_module_course_materials( $acmcm ); # associated this bib record with a course
+$e->commit;
+
+$apputils->simplereq('open-ils.courses', 'open-ils.courses.detach.material', $authtoken, 9998);
+
+$results = $e->search_asset_course_module_course_materials({id => 9998});
+is(scalar(@$results), 0, 'Successfully deleted acmcm');
+
+$results = $e->search_biblio_record_entry({tcn_source=>$temp_tcn_source,deleted=>0});
+is(scalar(@$results), 0, 'Successfully deleted bre');
+
+