From: Bill Erickson Date: Mon, 15 Jun 2020 21:00:06 +0000 (-0400) Subject: LPXXX Angular Volcopy X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=374c36d1bf4ab61c6d278d600abc27bfc8652784;p=working%2FEvergreen.git LPXXX Angular Volcopy Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html index e4ddab1fa4..673c9c7759 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html @@ -126,10 +126,13 @@
{{orgNode.target.shortname()}} - + {{sessionType}} + + +
@@ -201,10 +204,12 @@
- + + +
-
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts index 87d6faffe5..ce3fcacf18 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts @@ -39,7 +39,6 @@ export class VolEditComponent implements OnInit { batchVolSuffix: ComboboxEntry; batchVolLabel: ComboboxEntry; - recordVolLabels: string[] = []; autoBarcodeInProgress = false; useCheckdigit = false; @@ -69,7 +68,7 @@ export class VolEditComponent implements OnInit { this.deleteVolCount = null; this.deleteCopyCount = null; - this.fetchRecordVolLabels() + this.context.fetchRecordVolLabels() .then(_ => this.fetchBibParts()) .then(_ => this.addStubCopies()); @@ -88,25 +87,6 @@ export class VolEditComponent implements OnInit { this.volSuffixes = suffixes.filter(pfx => pfx.id() !== -1)); } - fetchRecordVolLabels(): Promise { - // NOTE: see https://bugs.launchpad.net/evergreen/+bug/1874897 - // for more on MARC call numbers and classification scheme. - - this.recordVolLabels = []; - const ids = this.context.getRecordIds(); - - // It only makes sense to fetch bib call numbers when we are - // working with exactly one record. - if (ids.length !== 1) { return Promise.resolve(); } - - return this.net.request( - 'open-ils.cat', - 'open-ils.cat.biblio.record.marc_cn.retrieve', ids[0] - ).toPromise().then(res => { - this.recordVolLabels = Object.values(res) - .map(blob => Object.values(blob)[0]).sort(); - }); - } fetchBibParts() { @@ -154,6 +134,7 @@ export class VolEditComponent implements OnInit { } volCountChanged(orgNode: HoldingsTreeNode, count: number) { + if (count === null) { return; } const diff = count - orgNode.children.length; if (diff > 0) { this.createVols(orgNode, diff); @@ -162,21 +143,6 @@ export class VolEditComponent implements OnInit { } } - createStubVol(orgId: number): IdlObject { - // Volume creation should only be available as an option when - // working with a specific call bib record. - const recId = +this.context.getRecordIds()[0]; - - const vol = this.idl.create('acn'); - vol.id(this.autoId--); - vol.isnew(true); - vol.record(recId); - vol.label(this.recordVolLabels[0] || ''); - vol.owning_lib(orgId); - - return vol; - } - existingVolCount(orgNode: HoldingsTreeNode): number { return orgNode.children.filter(volNode => !volNode.target.isnew()).length; } @@ -185,35 +151,8 @@ export class VolEditComponent implements OnInit { return volNode.children.filter(copyNode => !copyNode.target.isnew()).length; } - - - - createStubCopy(vol: IdlObject): IdlObject { - - const copy = this.idl.create('acp'); - copy.id(this.autoId--); - copy.isnew(true); - copy.circ_lib(vol.owning_lib()); - copy.call_number(vol.id()); - copy.deposit(0); - copy.price(0); - copy.deposit_amount(0); - copy.fine_level(2); // Normal - copy.loan_duration(2); // Normal - copy.location(1); // Stacks - copy.circulate('t'); - copy.holdable('t'); - copy.opac_visible('t'); - copy.ref('f'); - copy.mint_condition('t'); - copy.parts([]); - - // TODO: status / fast-add / defaults? - - return copy; - } - copyCountChanged(volNode: HoldingsTreeNode, count: number) { + if (count === null) { return; } const diff = count - volNode.children.length; if (diff > 0) { this.createCopies(volNode, diff); @@ -240,7 +179,7 @@ export class VolEditComponent implements OnInit { // Our context assumes copies are fleshed with volumes const vol = volNode.target; - const copy = this.createStubCopy(vol); + const copy = this.context.createStubCopy(vol); copy.call_number(vol); this.context.findOrCreateCopyNode(copy); } @@ -251,10 +190,10 @@ export class VolEditComponent implements OnInit { for (let i = 0; i < count; i++) { // This will vivify the volNode if needed. - const vol = this.createStubVol(orgNode.target.id()) + const vol = this.context.createStubVol(orgNode.target.id()) // Our context assumes copies are fleshed with volumes - const copy = this.createStubCopy(vol); + const copy = this.context.createStubCopy(vol); copy.call_number(vol); this.context.findOrCreateCopyNode(copy); } @@ -280,7 +219,7 @@ export class VolEditComponent implements OnInit { nodes.forEach(volNode => { if (volNode.children.length == 0) { const vol = volNode.target; - const copy = this.createStubCopy(vol); + const copy = this.context.createStubCopy(vol); copy.call_number(vol); this.context.findOrCreateCopyNode(copy); } diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts index ec145c632d..68e53cdae8 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts @@ -7,9 +7,10 @@ import {OrgService} from '@eg/core/org.service'; import {NetService} from '@eg/core/net.service'; import {AuthService} from '@eg/core/auth.service'; import {PcrudService} from '@eg/core/pcrud.service'; -import {HoldingsService} from '@eg/staff/share/holdings/holdings.service'; +import {HoldingsService, CallNumData} from '@eg/staff/share/holdings/holdings.service'; import {VolCopyContext} from './volcopy'; import {ProgressInlineComponent} from '@eg/share/dialog/progress-inline.component'; +import {AnonCacheService} from '@eg/share/util/anon-cache.service'; const COPY_FLESH = { flesh: 1, @@ -18,6 +19,22 @@ const COPY_FLESH = { } } +interface EditSession { + + // Unset if editing in multi-record mode + record_id: number; + + // Adding to or creating new call numbers + raw: CallNumData[]; + + // Hide the volumes editor + hide_vols: boolean; + + // Hide the copy attrs editor. + hide_copies: boolean; +} + + @Component({ templateUrl: 'volcopy.component.html' }) @@ -25,6 +42,9 @@ export class VolCopyComponent implements OnInit { context: VolCopyContext; + hideVols: boolean; + hideCopies: boolean; + loading = true; @ViewChild('loadingProgress', {static: false}) loadingProgress: ProgressInlineComponent; @@ -39,12 +59,15 @@ export class VolCopyComponent implements OnInit { private net: NetService, private auth: AuthService, private pcrud: PcrudService, + private cache: AnonCacheService, private holdings: HoldingsService ) { } ngOnInit() { this.context = new VolCopyContext(); this.context.org = this.org; // inject; + this.context.idl = this.idl; // inject; + this.context.net = this.net; // inject; this.route.paramMap.subscribe( (params: ParamMap) => this.negotiateRoute(params)); @@ -63,6 +86,8 @@ export class VolCopyComponent implements OnInit { this.context.reset(); this.fetchHoldings() .then(_ => this.loadingProgress.increment()) + .then(_ => this.context.applyVolLabels()) + .then(_ => this.loadingProgress.increment()) .then(_ => this.holdings.fetchCallNumberClasses()) .then(_ => this.loadingProgress.increment()) .then(_ => this.holdings.fetchCallNumberPrefixes()) @@ -86,20 +111,112 @@ export class VolCopyComponent implements OnInit { } fetchHoldings(): Promise { - if (this.context.copyId) { - this.context.sessionType = 'copy'; - return this.fetchCopies(this.context.copyId); - } else if (this.context.volId) { - this.context.sessionType = 'vol'; - return this.fetchVols(this.context.volId); + if (this.context.session) { + this.context.sessionType = 'mixed'; + return this.fetchSession(this.context.session); } else if (this.context.recordId) { this.context.sessionType = 'record'; return this.fetchRecords(this.context.recordId); - } else if (this.context.session) { - this.context.sessionType = 'mixed'; + } else if (this.context.volId) { + this.context.sessionType = 'vol'; + return this.fetchVols(this.context.volId); + } else if (this.context.copyId) { + this.context.sessionType = 'copy'; + return this.fetchCopies(this.context.copyId); } } + fetchSession(session: string): Promise { + + return this.cache.getItem(session, 'edit-these-copies') + .then((editSession: EditSession) => { + + if (!editSession) { return; } + + console.log('EDIT SES', editSession); + + this.context.recordId = editSession.record_id; + this.hideVols = editSession.hide_vols === true; + this.hideCopies = editSession.hide_copies === true; + + const volsToFetch = []; + const volsToCreate = []; + editSession.raw.forEach((volData: CallNumData) => { + this.context.fastAdd = volData.fast_add === true; + + if (volData.callnumber > 0) { + volsToFetch.push(volData); + } else { + volsToCreate.push(volData); + } + }); + + let promise = Promise.resolve(); + if (volsToFetch.length > 0) { + promise = promise.then(_ => + this.fetchVolsStubCopies(volsToFetch)); + } + + if (volsToCreate.length > 0) { + promise = promise.then(_ => + this.createVolsStubCopies(volsToCreate)); + } + + return promise; + }); + } + + createVolsStubCopies(volDataList: CallNumData[]): Promise { + + const vols = []; + volDataList.forEach(volData => { + + const vol = this.context.createStubVol( + volData.owner || this.auth.user().ws_ou(), + this.context.recordId + ); + + if (volData.label) {vol.label(volData.label); } + + volData.callnumber = vol.id(); // wanted by addStubCopies + vols.push(vol); + this.context.findOrCreateVolNode(vol); + }); + + return this.addStubCopies(vols, volDataList) + .then(_ => this.context.setVolClassLabels(vols)); + } + + // Fetch vols by ID, but instead of retrieving their copies + // add a stub copy to each. + fetchVolsStubCopies(volDataList: CallNumData[]): Promise { + + const volIds = volDataList.map(volData => volData.callnumber); + const vols = []; + + return this.pcrud.search('acn', {id: volIds}) + .pipe(tap((vol: IdlObject) => vols.push(vol))).toPromise() + .then(_ => this.addStubCopies(vols, volDataList)); + } + + addStubCopies(vols: IdlObject[], volDataList: CallNumData[]): Promise { + + const copies = []; + vols.forEach(vol => { + const volData = volDataList.filter( + volData => volData.callnumber === vol.id())[0]; + + const copy = + this.context.createStubCopy(vol, {circLib: volData.owner}); + + this.context.findOrCreateCopyNode(copy); + copies.push(copy); + }); + + return this.context.setCopyStatus(copies); + } + + fetchCopies(copyIds: number | number[]): Promise { const ids = [].concat(copyIds); return this.pcrud.search('acp', {id: ids}, COPY_FLESH) @@ -205,8 +322,8 @@ export class VolCopyComponent implements OnInit { let method = 'open-ils.cat.asset.volume.fleshed.batch.update'; if (override) { method += '.override'; } - this.net.request('open-ils.cat', - method, this.auth.token(), volumes).toPromise() + this.net.request('open-ils.cat', method, this.auth.token(), + volumes, 1, {auto_merge_vols: 1, create_parts: 1}).toPromise() .then(resp => { diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts index 75d88cdcf2..b0c490d5ce 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts @@ -1,5 +1,6 @@ -import {IdlObject} from '@eg/core/idl.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; import {OrgService} from '@eg/core/org.service'; +import {NetService} from '@eg/core/net.service'; export class HoldingsTreeNode { children: HoldingsTreeNode[]; @@ -23,6 +24,8 @@ export class VolCopyContext { autoId = -1; holdings: HoldingsTree = new HoldingsTree(); org: OrgService; // injected + idl: IdlService; + net: NetService; sessionType: 'copy' | 'vol' | 'record' | 'mixed'; @@ -38,9 +41,13 @@ export class VolCopyContext { // Load specific copy by ID. copyId: number; + fastAdd: boolean; + volsToDelete: IdlObject[] = []; copiesToDelete: IdlObject[] = []; + recordVolLabels: string[] = []; + reset() { this.holdings = new HoldingsTree(); this.volsToDelete = []; @@ -162,4 +169,181 @@ export class VolCopyContext { return true; } + + // This is the MARC CN label that will appear in the batch selector. + fetchRecordVolLabels(): Promise { + // NOTE: see https://bugs.launchpad.net/evergreen/+bug/1874897 + // for more on MARC call numbers and classification scheme. + + this.recordVolLabels = []; + const ids = this.getRecordIds(); + + // It only makes sense to fetch bib call numbers when we are + // working with exactly one record. + if (ids.length !== 1) { return Promise.resolve(); } + + return this.net.request( + 'open-ils.cat', + 'open-ils.cat.biblio.record.marc_cn.retrieve', ids[0] + ).toPromise().then(res => { + this.recordVolLabels = Object.values(res) + .map(blob => Object.values(blob)[0]).sort(); + }); + } + + createStubVol(orgId: number, recordId?: number): IdlObject { + // Volume creation should only be available as an option when + // working with a specific call bib record. + const recId = recordId || +this.getRecordIds()[0]; + + const vol = this.idl.create('acn'); + vol.id(this.autoId--); + vol.isnew(true); + vol.record(recId); + vol.label(this.recordVolLabels[0] || ''); + vol.owning_lib(Number(orgId)); + + return vol; + } + + createStubCopy(vol: IdlObject, options?: any): IdlObject { + if (!options) { options = {}; } + + const copy = this.idl.create('acp'); + copy.id(this.autoId--); + copy.isnew(true); + copy.circ_lib(Number(options.circLib || vol.owning_lib())); + copy.call_number(vol); + copy.deposit(0); + copy.price(0); + copy.deposit_amount(0); + copy.fine_level(2); // Normal + copy.loan_duration(2); // Normal + copy.location(1); // Stacks + copy.circulate('t'); + copy.holdable('t'); + copy.opac_visible('t'); + copy.ref('f'); + copy.mint_condition('t'); + copy.parts([]); + + // TODO: defaults? + + return copy; + } + + + // Applies label_class values to a batch of volumes, followed by + // applying labels to vols that need it. + setVolClassLabels(vols: IdlObject[]): Promise { + + const orgIds: any = {}; + vols.forEach(vol => orgIds[vol.owning_lib()] = true); + + // Serialize + let promise = Promise.resolve(); + + // TODO: if there is a local default value (ws setting?) + // apply it here and bypass the network call. + + const volsWantLabels = []; + Object.keys(orgIds).forEach(orgId => { + promise = promise.then(_ => { + + return this.org.settings( + 'cat.default_classification_scheme', Number(orgId)) + .then(sets => { + + const orgVols = vols.filter(v => v.owning_lib() === orgId); + orgVols.forEach(vol => { + vol.label_class( + sets['cat.default_classification_scheme'] || 1 + ); + if (!vol.label()) { volsWantLabels.push(vol); } + }); + }); + }); + }); + + return promise; + } + + // Apply labels to volumes based on the appropriate MARC call number. + applyVolLabels(): Promise { + + // Serialize + let promise = Promise.resolve(); + const vols = this.volNodes() + .map(volNode => volNode.target).filter(vol => !vol.label()); + + vols.forEach(vol => { + + // Avoid unnecessary lookups. + // Note the label may have been applied to this volume + // in a previous iteration of this loop. + if (vol.label()) { return; } + + promise = promise.then(_ => { + return this.net.request( + 'open-ils.cat', + 'open-ils.cat.biblio.record.marc_cn.retrieve', + vol.record(), vol.label_class()).toPromise() + + .then(cnList => { + // Use '_' as a placeholder to indicate when a + // vol has already been addressed. + let label = '_'; + + if (cnList.length > 0) { + const field = Object.keys(cnList[0])[0]; + label = cnList[0][field]; + } + + // Avoid making duplicate marc_cn calls by applying + // the label to all vols that apply. + vols.forEach(vol2 => { + if (vol2.record() === vol.record() && + vol2.label_class() === vol.label_class()) { + vol.label(label); + } + }); + }); + }); + }); + + return promise.then(_ => { + // Remove the placeholder label + vols.forEach(vol => { + if (vol.label() === '_') { vol.label(''); } + }); + }); + } + + // Sets the default copy status for a batch of copies. + setCopyStatus(copies: IdlObject[]): Promise { + + const setting = this.fastAdd ? + 'cat.default_copy_status_fast' : + 'cat.default_copy_status_normal' + + const orgIds: any = {}; + copies.forEach(copy => orgIds[copy.circ_lib()] = true); + + let promise = Promise.resolve(); + Object.keys(orgIds).forEach(orgId => { + + promise = promise.then(_ => + this.org.settings(setting, Number(orgId)) + ).then(sets => { + // 0 == Available; 5 == In Process + const stat = sets[setting] || (this.fastAdd ? 0 : 5); + const orgCopies = + copies.filter(copy => copy.circ_lib() === orgId); + orgCopies.forEach(copy => copy.status(stat)); + }); + }); + + return promise; + } + } diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts index 6b42543382..8044b5af4c 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts @@ -9,11 +9,12 @@ import {AuthService} from '@eg/core/auth.service'; import {EventService} from '@eg/core/event.service'; import {PcrudService} from '@eg/core/pcrud.service'; -interface NewCallNumData { +export interface CallNumData { owner?: number; label?: string; fast_add?: boolean; barcode?: string; + callnumber?: number; } @Injectable() @@ -35,7 +36,7 @@ export class HoldingsService { spawnAddHoldingsUi( recordId: number, // Bib record ID addToCallNums?: number[], // Add copies to / modify existing CNs - callNumData?: NewCallNumData[], // Creating new call numbers + callNumData?: CallNumData[], // Creating new call numbers hideCopies?: boolean) { // Hide the copy edit pane const raw: any[] = []; @@ -59,7 +60,7 @@ export class HoldingsService { return; } setTimeout(() => { - const url = `/eg/staff/cat/volcopy/${key}`; + const url = `/eg2/staff/cat/volcopy/edit/session/${key}`; window.open(url, '_blank'); }); }); diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js index 491b6ca10b..0d57d86625 100644 --- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js +++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js @@ -1378,7 +1378,8 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e } ).then(function(key) { if (key) { - var url = egCore.env.basePath + 'cat/volcopy/' + key; + //var url = egCore.env.basePath + 'cat/volcopy/' + key; + var url = '/eg2/staff/cat/volcopy/session/' + key; $timeout(function() { $window.open(url, '_blank') }); } else { alert('Could not create anonymous cache key!');