Revert "LP1929741 Finetuning LMOR based on LP comments"
authorChris Sharp <csharp@georgialibraries.org>
Tue, 16 Nov 2021 22:12:16 +0000 (17:12 -0500)
committerChris Sharp <csharp@georgialibraries.org>
Tue, 16 Nov 2021 22:12:16 +0000 (17:12 -0500)
This reverts commit f7d72c50822df9cd8e75a8abaa03ae0335c67721.

Open-ILS/src/eg2/src/app/staff/acq/picklist/upload.component.html
Open-ILS/src/eg2/src/app/staff/acq/picklist/upload.component.ts
Open-ILS/src/eg2/src/app/staff/acq/picklist/upload.service.ts

index 567b8fc..3feac3f 100644 (file)
@@ -5,6 +5,15 @@
   <div class="ml-auto mr-3"><a i18n href="/eg/staff/acq/legacy/picklist/upload">Legacy Upload Interface</a></div>
 </div>
 
+<div class="row mb-3" *ngIf="importSelection()">
+  <div class="col-lg-2" *ngIf="selectedQueue">
+    <button class="btn btn-info label-with-material-icon"
+      routerLink="/staff/cat/vandelay/queue/{{recordType}}/{{selectedQueue.id}}">
+      <span class="material-icons">arrow_back</span>
+      <span i18n>Return to Queue</span>
+    </button>
+  </div>
+</div>
 
 <eg-alert-dialog #dupeQueueAlert i18n-dialogBody 
   dialogBody="A queue with the requested name already exists.">
@@ -69,6 +78,7 @@
     </div>
       <div class="col-lg-3">
         <eg-org-select
+        [initialOrgId]="1"
         [applyOrgId]="orderingAgency"
         (onChange)="orgOnChange($event)">
         </eg-org-select>
         id="year-select"
         [startId]="selectedFiscalYear"
         [entries]="formatEntries('fiscalYears')"
-        [required]="true"
         (onChange)="selectEntry($event, 'fiscalYears')">
       </eg-combobox>
     </div>
         id="sl-select"
         [startId]="selectedSelectionList"
         [entries]="formatEntries('selectionLists')"
-        (onChange)="selectedSelectionList=$event" i18n-placeholder
-        [allowFreeText]="true">
+        (onChange)="selectEntry($event, 'selectionLists')">
       </eg-combobox>
     </div>
   </div>
 
   <div class="row">
     <div class="col-lg-3">
-      <label for="source-select" i18n>Record Source</label>
+      <label for="source-select" i18n>Select a Record Source</label>
     </div>
     <div class="col-lg-3">
       <eg-combobox #bibSourceSelector
         id="source-select"
         [entries]="formatEntries('bibSources')" 
         (onChange)="selectEntry($event, 'bibSources')"
+        [required]="true"
         [startId]="selectedBibSource">
       </eg-combobox>
     </div>
   </div>
   <div class="col-lg-3">
     <input class="form-check-input" type="checkbox" 
+    [required]="true"
       id="import-non-matching"
       [(ngModel)]="importNonMatching">
   </div>
         id="match-set-select"
         [entries]="formatEntries('matchSets')" 
         [disabled]="(selectedQueue && !selectedQueue.freetext) || importSelection()"
+        [required]="true"
         [startId]="selectedMatchSet || defaultMatchSet"
         (onChange)="selectEntry($event, 'matchSets')">
       </eg-combobox>
     <div class="col-lg-3">
       <eg-combobox #mergeProfileSelector
         id="merge-profiles"
+        [required]="true"
         [entries]="formatEntries('mergeProfiles')"
         (onChange)="selectEntry($event, 'mergeProfiles')"
         [startId]="selectedMergeProfile">
     <div class="col-lg-3">
       <eg-combobox [entries]="formatEntries('activeQueues')"
         id="queue-select"
+        [startId]="startQueueId"
         [startIdFiresOnChange]="true"
+        [disabled]="startQueueId"
         (onChange)="selectedQueue=$event" i18n-placeholder
         [required]="true"
         [allowFreeText]="true">
         (click)="upload()" i18n>Upload</button>
     </div>
   </div>
-  <div class="row" [hidden]="!showProgress || uploadComplete">
+  <div class="row" [hidden]="!showProgress || importSelection()">
     <div class="col-lg-3">
-      <label i18n>Upload File to Server</label>
+      <label i18n>Upload to Server</label>
     </div>
     <div class="col-lg-6">
       <eg-progress-inline #uploadProgress></eg-progress-inline>
     </div>
   </div>
  
-
-  <div class="col-lg-6 offset-lg-3" [hidden]="!uploadProcessing || uploadComplete">
-    <h2><label i18n><i>Processing...</i></label></h2>
+  <div class="col-lg-6" [hidden]="!isUploading">
+    <label i18n>Go To:</label>
   </div>
-
-  <div class="row" [hidden]="!uploadComplete">
-    <div class="col-lg-3 offset-lg-3">
-      <h2><label i18n>Upload Complete!</label></h2>
-    </div>
-    </div>
-    <div class="row" [hidden]="!uploadComplete">
-      <div class="col-sm-1 offset-lg-3">
-        <label i18n>Go to:</label>
-    </div>
-    <div><a routerLink="/staff/cat/vandelay/queue/{{recordType}}/{{activeQueueId}}" target="_blank" i18n>Queue</a></div>
-    <div class="col-sm-1" [hidden]="!selectedSelectionList"><a href="/eg/staff/acq/legacy/picklist/view/{{activeSelectionListId}}" target="_blank">Selection List</a></div>
-    <div class="col-sm-2" [hidden]="!createPurchaseOrder"><a href="/eg/staff/acq/legacy/po/view/{{newPO}}" target="_blank">Purchase Order</a></div>
+  <div class="row" [hidden]="!isUploading">
+    <div class="col-lg-6 offset-lg-3">
+      <a routerLink="/staff/cat/vandelay/queue/{{recordType}}/{{activeQueueId}}" target="_blank" i18n>Queue</a>
     </div>
+  </div>
+</div>
\ No newline at end of file
index 86b6918..9c1a32d 100644 (file)
@@ -26,7 +26,6 @@ const TEMPLATE_SETTING_NAME = 'eg.acq.picklist.upload.templates';
 const TEMPLATE_ATTRS = [
     'createPurchaseOrder',
     'activatePurchaseOrder',
-    'selectedProvider',
     'orderingAgency',
     'selectedFiscalYear',
     'loadItems',
@@ -56,6 +55,11 @@ const ORG_SETTINGS = [
     'acq.upload.default.vandelay.quality_ratio'
 ];
 
+interface ImportOptions {
+    session_key: string;
+    overlay_map?: {[qrId: number]: /* breId */ number};
+    exit_early: boolean;
+}
 
 @Component({
   templateUrl: './upload.component.html'
@@ -64,14 +68,16 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
 
     settings: Object = {};
     recordType: string;
-    selectedQueue: ComboboxEntry; 
+    selectedQueue: ComboboxEntry; // freetext enabled
 
+    // used for applying a default queue ID value when we have
+    // a load-time queue before the queue combobox entries exist.
+    startQueueId: number;
 
-    activeSelectionListId: number;
     activeQueueId: number;
     orderingAgency: number;
     selectedFiscalYear: number;
-    selectedSelectionList: ComboboxEntry;
+    selectedSelectionList: number;
     selectedBibSource: number;
     selectedProvider: number;
     selectedMatchSet: number;
@@ -79,7 +85,6 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
     selectedMergeProfile: number;
     selectedFallThruMergeProfile: number;
     selectedFile: File;
-    newPO: number;
 
     defaultMatchSet: string;
 
@@ -93,13 +98,20 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
     mergeOnBestMatch: boolean;
     minQualityRatio: number;
 
+    // True after the first upload, then remains true.
+    showProgress: boolean;
+
+    // Upload in progress.
     isUploading: boolean;
-    uploadProcessing: boolean;
+
+    // True only after successful upload
     uploadComplete: boolean;
 
+    // Upload / processsing session key
     // Generated by the server
     sessionKey: string;
 
+
     selectedTemplate: string;
     formTemplates: {[name: string]: any};
     newTemplateName: string;
@@ -108,6 +120,7 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
     @ViewChild('uploadProgress', { static: true })
         private uploadProgress: ProgressInlineComponent;
 
+    // Need these refs so values can be applied via external stimuli
     @ViewChild('formTemplateSelector', { static: true })
         private formTemplateSelector: ComboboxComponent;
     @ViewChild('bibSourceSelector', { static: true })
@@ -162,6 +175,7 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
         this.minQualityRatio = 0;
         this.recordType = 'bib';
         this.formTemplates = {};
+//To-do add default for fiscal year
         if (this.vlagent.importSelection) {
 
             if (!this.vlagent.importSelection.queue) {
@@ -171,8 +185,14 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
             }
 
             const queue = this.vlagent.importSelection.queue;
+            this.recordType = queue.queue_type();
             this.selectedMatchSet = queue.match_set();
 
+            // This will be propagated to selectedQueue as a combobox
+            // entry via the combobox
+            this.startQueueId = queue.id();
+
+
         }
     }
 
@@ -183,6 +203,8 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
     }
 
     ngOnDestroy() {
+        // Always clear the import selection when navigating away from
+        // the import page.
         this.clearSelection();
     }
 
@@ -202,8 +224,8 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
             this.vlagent.getProvidersList(),
             this.vlagent.getSelectionLists(),
             this.vlagent.getItemImportDefs(),
-           this.org.settings(['vandelay.default_match_set']).then(
-               s => this.defaultMatchSet = s['vandelay.default_match_set']),
+            this.org.settings(['vandelay.default_match_set']).then(
+                s => this.defaultMatchSet = s['vandelay.default_match_set']),
             this.loadTemplates()
         ];
 
@@ -238,6 +260,7 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
         return entries;
     }
 
+    // Format typeahead data sets
     formatEntries(etype: string): ComboboxEntry[] {
         const rtype = this.recordType;
         let list;
@@ -267,7 +290,8 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
                  break;
 
             case 'activeQueues':
-                list = (this.vlagent.allQueues[rtype] || []);
+                list = (this.vlagent.allQueues[rtype] || [])
+                        .filter(q => q.complete() === 'f');
                 break;
 
             case 'matchSets':
@@ -332,22 +356,34 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
        this.selectedFile = $event.target.files[0];
     }
 
+    // Required form data varies depending on context.
     hasNeededData(): boolean {
-        return this.selectedQueue &&
-        Boolean(this.selectedFile) &&
-        Boolean(this.selectedFiscalYear) &&
-        Boolean(this.selectedProvider) &&
-        Boolean(this.orderingAgency);
+        if (this.vlagent.importSelection) {
+            return this.importActionSelected();
+        } else {
+            return this.selectedQueue &&
+                Boolean(this.recordType) && Boolean(this.selectedFile);
+        }
+    }
+
+    importActionSelected(): boolean {
+        return this.importNonMatching
+            || this.mergeOnExact
+            || this.mergeOnSingleMatch
+            || this.mergeOnBestMatch;
     }
-    
+
+    // 1. create queue if necessary
+    // 2. upload MARC file
+    // 3. Enqueue MARC records
+    // 4. Import records
     upload() {
         this.sessionKey = null;
+        this.showProgress = true;
         this.isUploading = true;
         this.uploadComplete = false;
         this.resetProgressBars();
 
-        this.resolveSelectionList(),
         this.resolveQueue()
         .then(
             queueId => {
@@ -376,9 +412,13 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
         this.uploadProgress.update({value: 0, max: 1});
     }
 
+    // Extract selected queue ID or create a new queue when requested.
     resolveQueue(): Promise<number> {
 
         if (this.selectedQueue.freetext) {
+            // Free text queue selector means create a new entry.
+            // TODO: first check for name dupes
+
             return this.vlagent.createQueue(
                 this.selectedQueue.label,
                 this.recordType,
@@ -404,28 +444,10 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
         }
     }
 
-    resolveSelectionList(): Promise<any> {
-        if (!this.selectedSelectionList) {
-            return Promise.resolve()
-        }
-        if (this.selectedSelectionList.id) {
-            this.activeSelectionListId = this.selectedSelectionList.id
-        }
-        if (this.selectedSelectionList.freetext) {
-
-            return this.vlagent.createSelectionList(
-                this.selectedSelectionList.label,
-                this.orderingAgency
-            ).then(
-                value => this.activeSelectionListId = value
-            );
-        }
-        return Promise.resolve(this.activeSelectionListId); 
-    }
-
     uploadFile(): Promise<any> {
 
         if (this.vlagent.importSelection) {
+            // Nothing to upload when processing pre-queued records.
             return Promise.resolve();
         }
 
@@ -464,12 +486,10 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
 
     processUpload():  Promise<any> {
 
-        this.uploadProcessing = true;
-
         if (this.vlagent.importSelection) {
             return Promise.resolve();
         }
-        
+
         let spoolType = this.recordType;
 
         const vandelayOptions = {
@@ -483,8 +503,10 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
             bib_source: this.selectedBibSource,
             create_assets: this.loadItems,
             queue_name: this.selectedQueue.label
+
         }
 
+        
         const args = {
             
             provider: this.selectedProvider,
@@ -492,7 +514,7 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
             create_po: this.createPurchaseOrder,
             activate_po: this.activatePurchaseOrder,
             fiscal_year: this.selectedFiscalYear,
-            picklist: this.activeSelectionListId,
+            picklist: this.selectedSelectionList,
             vandelay: vandelayOptions
         }
 
@@ -504,15 +526,9 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
                 'open-ils.acq', method,
                 this.auth.token(), this.sessionKey, args
             ).subscribe(
-                progress => {
-                    const resp = this.evt.parse(progress);
-                    console.log(progress);
-                    if (resp) { console.error(resp); return reject();}
-                    if (progress.complete) {
-                        this.uploadProcessing = false;
-                        this.uploadComplete = true;
-                    }
-                    if (progress.purchase_order) {this.newPO = progress.purchase_order.id();}
+                tracker => {
+                    const e = this.evt.parse(tracker);
+                    if (e) { console.error(e); return reject(); }
                 }
             );
         });
@@ -520,9 +536,12 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
 
     clearSelection() {
         this.vlagent.importSelection = null;
-        this.activeSelectionListId = null;
+        this.startQueueId = null;
     }
 
+    openQueue() {
+        console.log('opening queue ' + this.activeQueueId);
+    }
 
     saveTemplate() {
 
@@ -556,19 +575,27 @@ export class UploadComponent implements OnInit, AfterViewInit, OnDestroy {
         this.selectedTemplate = entry.label; // label == name
 
         if (entry.freetext) {
+            // User is entering a new template name.
+            // Nothing to apply.
             return;
         }
 
+        // User selected an existing template, apply it to the form.
+
         const template = this.formTemplates[entry.id];
 
+        // Copy the template values into "this"
         TEMPLATE_ATTRS.forEach(key => this[key] = template[key]);
 
+        // Some values must be manually passed to the combobox'es
+
         this.bibSourceSelector.applyEntryId(this.selectedBibSource);
         this.matchSetSelector.applyEntryId(this.selectedMatchSet);
         this.providerSelector.applyEntryId(this.selectedProvider);
         this.fiscalYearSelector.applyEntryId(this.selectedFiscalYear);
         this.mergeProfileSelector.applyEntryId(this.selectedMergeProfile);
-        this.fallThruMergeProfileSelector.applyEntryId(this.selectedFallThruMergeProfile);
+        this.fallThruMergeProfileSelector
+            .applyEntryId(this.selectedFallThruMergeProfile);
     }
 
     deleteTemplate() {
index 0cf0718..a586166 100644 (file)
@@ -11,7 +11,7 @@ import {PcrudService} from '@eg/core/pcrud.service';
 import {PermService} from '@eg/core/perm.service';
 import {EventService} from '@eg/core/event.service';
 import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
-import {VandelayImportSelection} from '@eg/staff/cat/vandelay/vandelay.service'
+import {VandelayImportSelection, VANDELAY_EXPORT_PATH} from '@eg/staff/cat/vandelay/vandelay.service'
 
 
 @Injectable()
@@ -26,12 +26,16 @@ export class PicklistUploadService {
     providersList: IdlObject[];
     fiscalYears: IdlObject[];
     selectionLists: IdlObject[];
-    queueType: string;
-    recordType: string;
-
 
+    // Used for tracking records between the queue page and
+    // the import page.  Fields managed externally.
     importSelection: VandelayImportSelection;
 
+    // Track the last grid offset in the queue page so we
+    // can return the user to the same page of data after
+    // going to the matches page.
+    queuePageOffset: number;
+
     constructor(
         private http: HttpClient,
         private idl: IdlService,
@@ -46,8 +50,7 @@ export class PicklistUploadService {
         this.allQueues = {};
         this.matchSets = {};
         this.importSelection = null;
-        this.queueType = 'acq';
-        this.recordType = 'bib';
+        this.queuePageOffset = 0;
     }
 
     getAttrDefs(dtype: string): Promise<IdlObject[]> {
@@ -106,7 +109,7 @@ export class PicklistUploadService {
             return lists;
         });
     }
-
+    // Returns a promise resolved with the list of queues.
     getAllQueues(qtype: string): Promise<IdlObject[]> {
         if (this.allQueues[qtype]) {
             return Promise.resolve(this.allQueues[qtype]);
@@ -114,9 +117,10 @@ export class PicklistUploadService {
             this.allQueues[qtype] = [];
         }
 
+        // could be a big list, invoke in streaming mode
         return this.net.request(
             'open-ils.vandelay',
-            `open-ils.vandelay.bib_queue.owner.retrieve`,
+            `open-ils.vandelay.${qtype}_queue.owner.retrieve`,
             this.auth.token()
         ).pipe(tap(
             queue => this.allQueues[qtype].push(queue)
@@ -164,9 +168,10 @@ export class PicklistUploadService {
         });
     }
 
+    // todo: differentiate between biblio and authority a la queue api
     getMatchSets(mtype: string): Promise<IdlObject[]> {
 
-        const mstype = 'biblio'
+        const mstype = mtype.match(/bib/) ? 'biblio' : 'authority';
 
         if (this.matchSets[mtype]) {
             return Promise.resolve(this.matchSets[mtype]);
@@ -185,57 +190,88 @@ export class PicklistUploadService {
     }
 
 
+
+
+
+
+    // Create a queue and return the ID of the new queue via promise.
     createQueue(
         queueName: string,
-        queueType: string,
+        recordType: string,
         importDefId: number,
         matchSet: number): Promise<number> {
 
-        const method = `open-ils.vandelay.bib_queue.create`;
-        queueType = 'acq';
+        const method = `open-ils.vandelay.${recordType}_queue.create`;
 
+        let qType = recordType;
+        if (recordType.match(/acq/)) {
+            qType = 'bib';
+        }
 
         return new Promise((resolve, reject) => {
             this.net.request(
                 'open-ils.vandelay', method,
-                this.auth.token(), queueName, null, queueType,
+                this.auth.token(), queueName, null, qType,
                 matchSet, importDefId
             ).subscribe(queue => {
                 const e = this.evt.parse(queue);
                 if (e) {
                     reject(e);
                 } else {
-                    this.allQueues['bib'].push(queue);
+                    // createQueue is always called after queues have
+                    // been fetched and cached.
+                    this.allQueues[qType].push(queue);
                     resolve(queue.id());
                 }
             });
         });
     }
 
-    createSelectionList(
-        picklistName: string,
-        picklistOrg: number
-    ): Promise<number> {
+    getQueuedRecords(queueId: number, queueType: string,
+      options?: any, limitToMatches?: boolean): Observable<any> {
 
-        const newpicklist = this.idl.create('acqpl');
-        newpicklist.owner(this.auth.user().id());
-        newpicklist.name(picklistName);
-        newpicklist.org_unit(picklistOrg)
-        
-        return new Promise((resolve, reject) => {
-            this.net.request(
-                'open-ils.acq', 'open-ils.acq.picklist.create',
-                this.auth.token(), newpicklist
-            ).subscribe((picklist) => {
-                if (this.evt.parse(picklist)) {
-                    console.error(picklist);
-                } else {
-                    console.log(picklist);
-                    resolve(picklist);
-                }
-            });
-        });
+        const qtype = queueType.match(/bib/) ? 'bib' : 'auth';
+
+        let method =
+          `open-ils.vandelay.${qtype}_queue.records.retrieve`;
+
+        if (limitToMatches) {
+            method =
+              `open-ils.vandelay.${qtype}_queue.records.matches.retrieve`;
+        }
+
+        return this.net.request('open-ils.vandelay',
+            method, this.auth.token(), queueId, options);
+    }
+
+    // Download a queue as a MARC file.
+    exportQueue(queue: IdlObject, nonImported?: boolean) {
+
+        const etype = queue.queue_type().match(/auth/) ? 'auth' : 'bib';
+
+        let url =
+          `${VANDELAY_EXPORT_PATH}?type=bib&queueid=${queue.id()}`;
+
+        let saveName = queue.name();
+
+        if (nonImported) {
+            url += '&nonimported=1';
+            saveName += '_nonimported';
+        }
+
+        saveName += '.mrc';
+
+        this.http.get(url, {responseType: 'text'}).subscribe(
+            data => {
+                saveAs(
+                    new Blob([data], {type: 'application/octet-stream'}),
+                    saveName
+                );
+            },
+            err  => {
+                console.error(err);
+            }
+        );
     }
-    
 }