<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.">
</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
const TEMPLATE_ATTRS = [
'createPurchaseOrder',
'activatePurchaseOrder',
- 'selectedProvider',
'orderingAgency',
'selectedFiscalYear',
'loadItems',
'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'
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;
selectedMergeProfile: number;
selectedFallThruMergeProfile: number;
selectedFile: File;
- newPO: number;
defaultMatchSet: string;
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;
@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 })
this.minQualityRatio = 0;
this.recordType = 'bib';
this.formTemplates = {};
+//To-do add default for fiscal year
if (this.vlagent.importSelection) {
if (!this.vlagent.importSelection.queue) {
}
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();
+
+
}
}
}
ngOnDestroy() {
+ // Always clear the import selection when navigating away from
+ // the import page.
this.clearSelection();
}
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()
];
return entries;
}
+ // Format typeahead data sets
formatEntries(etype: string): ComboboxEntry[] {
const rtype = this.recordType;
let list;
break;
case 'activeQueues':
- list = (this.vlagent.allQueues[rtype] || []);
+ list = (this.vlagent.allQueues[rtype] || [])
+ .filter(q => q.complete() === 'f');
break;
case 'matchSets':
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 => {
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,
}
}
- 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();
}
processUpload(): Promise<any> {
- this.uploadProcessing = true;
-
if (this.vlagent.importSelection) {
return Promise.resolve();
}
-
+
let spoolType = this.recordType;
const vandelayOptions = {
bib_source: this.selectedBibSource,
create_assets: this.loadItems,
queue_name: this.selectedQueue.label
+
}
+
const args = {
provider: this.selectedProvider,
create_po: this.createPurchaseOrder,
activate_po: this.activatePurchaseOrder,
fiscal_year: this.selectedFiscalYear,
- picklist: this.activeSelectionListId,
+ picklist: this.selectedSelectionList,
vandelay: vandelayOptions
}
'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(); }
}
);
});
clearSelection() {
this.vlagent.importSelection = null;
- this.activeSelectionListId = null;
+ this.startQueueId = null;
}
+ openQueue() {
+ console.log('opening queue ' + this.activeQueueId);
+ }
saveTemplate() {
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() {
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()
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,
this.allQueues = {};
this.matchSets = {};
this.importSelection = null;
- this.queueType = 'acq';
- this.recordType = 'bib';
+ this.queuePageOffset = 0;
}
getAttrDefs(dtype: string): Promise<IdlObject[]> {
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]);
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)
});
}
+ // 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]);
}
+
+
+
+
+ // 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);
+ }
+ );
}
-
}