Temporary Fields upon Association/Disassociation
authorKyle Huckins <khuckins@catalyte.io>
Tue, 19 Nov 2019 21:18:44 +0000 (21:18 +0000)
committerJane Sandberg <sandbej@linnbenton.edu>
Sat, 8 Aug 2020 15:20:33 +0000 (08:20 -0700)
- Optionally apply temporary Call Number, Circ Modifier, Item
Status, and Shelving Location when associating an item with a
course.
- Reapply original values of the above-mentioned fields when
disassociating an item from a course.

Signed-off-by: Kyle Huckins <khuckins@catalyte.io>
 Changes to be committed:
modified:   Open-ILS/examples/fm_IDL.xml
modified:   Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-associate-material.component.html
modified:   Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-associate-material.component.ts
modified:   Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-reserves.module.ts
modified:   Open-ILS/src/sql/Pg/upgrade/XXXX.schema.course-materials-module.sql

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-associate-material.component.html
Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-associate-material.component.ts
Open-ILS/src/eg2/src/app/staff/admin/server/course-reserves/course-reserves.module.ts
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.course-materials-module.sql

index 12f064f..4d63fff 100644 (file)
@@ -3107,10 +3107,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Course" name="course" reporter:datatype="link" />
             <field reporter:label="Item" name="item" 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" />
+            <field reporter:label="Original Shelving Location" name="original_location" reporter:datatype="link" />
+            <field reporter:label="Original Callnumber" name="original_callnumber" reporter:datatype="link" />
         </fields>
         <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="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" />
+            <link field="original_location" reltype="has_a" key="id" map="" class="acpl" />
         </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
index 43fdc82..488eea7 100644 (file)
@@ -13,7 +13,7 @@
     </button>
   </div>
   <div class="modal-body">
-    <div class="row">
+    <div class="row mt-3">
       <div class="col-md-4">
         <div class="input-group">
           <div class="input-group-prepend">
         <button class="btn btn-outline-dark" (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-md-6">
+        <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"
+            (input)="isModifyingCallNumber = true"/>
+          <div class="input-group-append">
+            <div class="input-group-text">
+              <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
+                aria-label="Checkbox for setting a temporary Call Number" />
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-6">
+        <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"
+            [asyncSupportsEmptyTermClick]="true"
+            (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
+          </eg-combobox>
+          <div class="input-group-append">
+            <div class="input-group-text">
+              <input type="checkbox" [(ngModel)]="isModifyingCircMod"
+                aria-label="Checkbox for setting a temporary Circulation Modifier" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="row mt-3">
+      <div class="col-md-6">
+        <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"
+            [asyncSupportsEmptyTermClick]="true"
+            (onChange)="tempStatus = $event.id; isModifyingStatus = true">
+          </eg-combobox>
+          <div class="input-group-append">
+            <div class="input-group-text">
+              <input type="checkbox" [(ngModel)]="isModifyingStatus"
+                aria-label="Checkbox for setting a temporary Item Status" />
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col-md-6">
+        <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"
+            [(ngModel)]="tempLocation" (oninput)="isModifyingLocation = true">
+          </eg-item-location-select>
+          <div class="input-group-append">
+            <div class="input-group-text">
+              <input type="checkbox" [(ngModel)]="isModifyingLocation"
+                aria-label="Checkbox for setting a temporary Shelving Location" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
     <div class="mt-3">
       <eg-grid #materialGrid [dataSource]="gridDataSource">
         <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
         <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" label="Circulation Modifier" i18n-label></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>
       </a>
     </span>
   </ng-template>
+  <ng-template #idlClassLabel let-r="result" i18n>
+    {{r.label}}
+  </ng-template>
 </ng-template>
\ No newline at end of file
index cba573f..cd40056 100644 (file)
@@ -3,6 +3,7 @@ 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';
@@ -29,6 +30,14 @@ export class CourseAssociateMaterialComponent extends DialogComponent {
     @Input() table_name = "Course Materials";
     @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;
     currentCourse: IdlObject;
     materials: any[];
     gridDataSource: GridDataSource;
@@ -39,6 +48,7 @@ export class CourseAssociateMaterialComponent extends DialogComponent {
         private net: NetService,
         private pcrud: PcrudService,
         private org: OrgService,
+        private evt: EventService,
         private modal: NgbModal,
         private toast: ToastService
     ) {
@@ -58,10 +68,11 @@ export class CourseAssociateMaterialComponent extends DialogComponent {
             this.gridDataSource.data.splice(this.gridDataSource.data.indexOf(item, 0), 1);
             item_ids.push(item.id())
         });
-        this.pcrud.search('acmcm', {course: this.currentCourse.id(), item: item_ids}).subscribe(res => {
-            res.isdeleted(true);
-            this.pcrud.autoApply(res).subscribe(
+        this.pcrud.search('acmcm', {course: this.currentCourse.id(), item: item_ids}).subscribe(material => {
+            material.isdeleted(true);
+            this.pcrud.autoApply(material).subscribe(
                 val => {
+                    this.resetItemFields(material);
                     console.debug('deleted: ' + val);
                     this.deleteSuccessString.current().then(str => this.toast.success(str));
                 },
@@ -73,24 +84,76 @@ export class CourseAssociateMaterialComponent extends DialogComponent {
         });
     }
 
+    resetItemFields(material) {
+        this.pcrud.retrieve('acp', material.item(),
+            {flesh: 3, flesh_fields: {acp: ['call_number']}}).subscribe(copy => {
+            if (material.original_status()) {
+                copy.status(material.original_status());
+            }
+            if (copy.circ_modifier() != material.original_circ_modifier()) {
+                copy.circ_modifier(material.original_circ_modifier());
+            }
+            if (material.original_location()) {
+                copy.location(material.original_location());
+            }
+            if (material.original_callnumber()) {
+                this.pcrud.retrieve('acn', material.original_callnumber()).subscribe(cn => {
+                    this.updateItem(copy, cn.label(), true);
+                });
+            } else {
+                this.updateItem(copy, copy.call_number().label(), false);
+            }
+        });
+    }
+
     associateItem(barcode, relationship) {
         if (barcode) {
-            this.pcrud.search('acp', {barcode: barcode}).subscribe(item => {
+            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);
-                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));
-                }
+                   let new_cn = item.call_number().label();
+                   if (this.tempCallNumber) new_cn = this.tempCallNumber;
+                    this.updateItem(item, new_cn, this.isModifyingCallNumber);
+                    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));
+                    }
                     this.fetchItem(item.id(), relationship);
+
+                    // 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));
                 });
@@ -98,6 +161,41 @@ export class CourseAssociateMaterialComponent extends DialogComponent {
         }
     }
 
+    updateItem(item: IdlObject, call_number, updatingVolume) {
+        return new Promise((resolve, reject) => {
+            this.pcrud.update(item).subscribe(item_id => {
+                if (updatingVolume) {
+                    let cn = item.call_number();
+                    return this.net.request(
+                        'open-ils.cat', 'open-ils.cat.call_number.find_or_create',
+                        this.auth.token(), call_number, cn.record(),
+                        this.currentCourse.owning_lib(), cn.prefix(), cn.suffix(),
+                        cn.label_class()
+                    ).subscribe(res => {
+                        let event = this.evt.parse(res);
+                        if (event) return;
+                        return this.net.request(
+                            'open-ils.cat', 'open-ils.cat.transfer_copies_to_volume',
+                            this.auth.token(), res.acn_id, [item.id()]
+                        ).subscribe(transfered_res => {
+                            console.debug("Copy transferred to volume with code " + transfered_res);
+                        }, err => {
+                            reject(err);
+                        }, () => {
+                            resolve(item);
+                        });
+                    }, err => {
+                        reject(err);
+                    }, () => {
+                        resolve(item);
+                    });
+                } else {
+                    return this.pcrud.update(item);
+                }
+            });
+        });
+    }
+
     fetchMaterials(pager: Pager): Observable<any> {
         return new Observable<any>(observer => {
             this.materials.forEach(material => {
index 0518730..c87331b 100644 (file)
@@ -4,6 +4,7 @@ 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';
 
 @NgModule({
   declarations: [
@@ -13,6 +14,7 @@ import {CourseReservesRoutingModule} from './routing.module';
   imports: [
     AdminCommonModule,
     CourseReservesRoutingModule,
+    ItemLocationSelectModule,
     TreeModule
   ],
   exports: [
index 30fe913..47d7a95 100644 (file)
@@ -21,7 +21,11 @@ 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),
-    relationship    TEXT
+    relationship    TEXT,
+    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
 );
 
 CREATE TABLE asset.course_module_non_cat_course_materials (