From: Bill Erickson Date: Wed, 1 Jul 2020 18:32:39 +0000 (-0400) Subject: LPXXX Angular Volcopy X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=2917f7b371495990650d20be945ba4f46f64c23f;p=working%2FEvergreen.git LPXXX Angular Volcopy Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.html b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.html index 4245d1e335..bede5f58c2 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.html +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.html @@ -409,12 +409,10 @@

Statistics

-
Add Item Tags
-
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.ts index e520736687..241a25fcc1 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.ts @@ -17,6 +17,8 @@ import {FormatService} from '@eg/core/format.service'; import {StringComponent} from '@eg/share/string/string.component'; import {CopyAlertsDialogComponent } from '@eg/staff/share/holdings/copy-alerts-dialog.component'; +import {CopyTagsDialogComponent + } from '@eg/staff/share/holdings/copy-tags-dialog.component'; import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component'; import {BatchItemAttrComponent} from '@eg/staff/share/holdings/batch-item-attr.component'; import {FileExportService} from '@eg/share/util/file-export.service'; @@ -67,6 +69,9 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit { @ViewChild('copyAlertsDialog', {static: false}) private copyAlertsDialog: CopyAlertsDialogComponent; + @ViewChild('copyTagsDialog', {static: false}) + private copyTagsDialog: CopyTagsDialogComponent; + @ViewChild('copyTemplateCbox', {static: false}) copyTemplateCbox: ComboboxComponent; @@ -366,6 +371,29 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit { ); } + openCopyTags() { + this.copyTagsDialog.mode = 'create'; + this.copyTagsDialog.inPlaceMode = true; + + this.copyTagsDialog.open({size: 'lg'}).subscribe(newTags => { + if (!newTags || newTags.length === 0) { return; } + + newTags.forEach(tag => { + this.context.copyList().forEach(copy => { + console.log('ADDING TAG ', tag); + /* + const a = this.idl.clone(newTag); + a.isnew(true); + a.copy(copy.id()); + if (!copy.copy_alerts()) { copy.copy_alerts([]); } + copy.copy_alerts().push(a); + copy.ischanged(true); + */ + }); + }); + }); + } + applyTemplate() { const entry = this.copyTemplateCbox.selected; if (!entry) { return; } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html index 86f35cb9e2..15040fdd99 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html @@ -47,6 +47,7 @@ + @@ -121,6 +122,11 @@ + + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts index ea22f29a91..320f5795cb 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts @@ -23,6 +23,8 @@ import {AnonCacheService} from '@eg/share/util/anon-cache.service'; import {HoldingsService} from '@eg/staff/share/holdings/holdings.service'; import {CopyAlertsDialogComponent } from '@eg/staff/share/holdings/copy-alerts-dialog.component'; +import {CopyTagsDialogComponent + } from '@eg/staff/share/holdings/copy-tags-dialog.component'; import {ReplaceBarcodeDialogComponent } from '@eg/staff/share/holdings/replace-barcode-dialog.component'; import {DeleteHoldingDialogComponent @@ -106,6 +108,8 @@ export class HoldingsMaintenanceComponent implements OnInit { private markMissingDialog: MarkMissingDialogComponent; @ViewChild('copyAlertsDialog', { static: true }) private copyAlertsDialog: CopyAlertsDialogComponent; + @ViewChild('copyTagsDialog', {static: false}) + private copyTagsDialog: CopyTagsDialogComponent; @ViewChild('replaceBarcode', { static: true }) private replaceBarcode: ReplaceBarcodeDialogComponent; @ViewChild('deleteHolding', { static: true }) @@ -851,6 +855,20 @@ export class HoldingsMaintenanceComponent implements OnInit { ); } + openItemTags(rows: HoldingsEntry[], mode: string) { + const copyIds = this.selectedCopyIds(rows); + if (copyIds.length === 0) { return; } + + this.copyTagsDialog.copyIds = copyIds; + this.copyTagsDialog.open({size: 'lg'}).subscribe( + modified => { + if (modified) { + this.hardRefresh(); + } + } + ); + } + openReplaceBarcodeDialog(rows: HoldingsEntry[]) { const ids = this.selectedCopyIds(rows); if (ids.length === 0) { return; } diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.html new file mode 100644 index 0000000000..b2c1ffaef5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.html @@ -0,0 +1,106 @@ + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.ts new file mode 100644 index 0000000000..e008b81b8c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/copy-tags-dialog.component.ts @@ -0,0 +1,236 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable, throwError, from, empty} from 'rxjs'; +import {tap, map, switchMap} from 'rxjs/operators'; +import {NetService} from '@eg/core/net.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {OrgService} from '@eg/core/org.service'; +import {StringComponent} from '@eg/share/string/string.component'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +/** + * Dialog for managing copy tags. + */ + +@Component({ + selector: 'eg-copy-tags-dialog', + templateUrl: 'copy-tags-dialog.component.html' +}) + +export class CopyTagsDialogComponent + extends DialogComponent implements OnInit { + + static autoId = -1; + + @Input() copyIds: number[] = []; + + // If there are multiple copyIds, only new tags may be applied. + // If there is only one copyId, then tags may be applied or removed. + mode: string; // create | manage + + // If true, no attempt is made to save the new tags to the + // database. It's assumed this takes place in the calling code. + // This is useful for creating tags for new copies. + @Input() inPlaceMode = false; + + // In 'create' mode, we may be adding notes to multiple copies. + copies: IdlObject[] = []; + + // In 'manage' mode we only handle a single copy. + copy: IdlObject; + + tagTypes: ComboboxEntry[]; + + curTag: ComboboxEntry = null; + curTagType: ComboboxEntry = null; + newTags: IdlObject[] = []; + tagMap: {[id: number]: IdlObject} = {}; + + tagDataSource: (term: string) => Observable; + + @ViewChild('successMsg', { static: true }) private successMsg: StringComponent; + @ViewChild('errorMsg', { static: true }) private errorMsg: StringComponent; + + constructor( + private modal: NgbModal, // required for passing to parent + private toast: ToastService, + private net: NetService, + private idl: IdlService, + private pcrud: PcrudService, + private org: OrgService, + private auth: AuthService) { + super(modal); // required for subclassing + } + + ngOnInit() { + + this.tagDataSource = term => { + if (!this.curTagType) { return empty(); } + + return this.pcrud.search( + 'acpt', { + tag_type: this.curTagType.id, + '-or': [ + {value: {'ilike': `%${term}%`}}, + {label: {'ilike': `%${term}%`}} + ] + }, + {order_by: {acpt: 'label'}} + ).pipe(map(copyTag => { + this.tagMap[copyTag.id()] = copyTag; + return {id: copyTag.id(), label: copyTag.label()}; + })); + }; + } + + /** + */ + open(args: NgbModalOptions): Observable { + this.copy = null; + this.copies = []; + this.newTags = []; + + if (this.copyIds.length === 0 && !this.inPlaceMode) { + return throwError('copy ID required'); + } + + // In manage mode, we can only manage a single copy. + // But in create mode, we can add tags to multiple copies. + + if (this.copyIds.length === 1) { + this.mode = 'manage'; + } else { + this.mode = 'create'; + } + + // Observify data loading + const obs = from( + this.getTagTypes() + .then(_ => this.getCopies()) + ); + + // Return open() observable to caller + return obs.pipe(switchMap(_ => super.open(args))); + } + + getTagTypes(): Promise { + if (this.tagTypes) { return Promise.resolve(); } + + this.tagTypes = []; + return this.pcrud.search('cctt', + {owner: this.org.ancestors(this.auth.user().ws_ou(), true)}, + {order_by: {cctt: 'label'}} + ).pipe(tap(tag => + this.tagTypes.push({id: tag.code(), label: tag.label()}) + )).toPromise(); + } + + getCopies(): Promise { + if (this.inPlaceMode) { return Promise.resolve(); } + + return this.pcrud.search('acp', {id: this.copyIds}, + {flesh: 1, flesh_fields: {acp: ['tags']}}, {atomic: true}) + .toPromise().then(copies => { + this.copies = copies; + if (copies.length === 1) { + this.copy = copies[0]; + } + }); + } + + removeTag(tag: IdlObject) { + this.newTags = this.newTags.filter(t => t.id() !== tag.id()); + + // TODO: delete existing maps where needed. + } + + addNew() { + if (!this.curTagType || !this.curTag) { return; } + + let tag; + + if (this.curTag.freetext) { + // Create a new tag w/ the provided tag text. + tag = this.idl.create('acpt'); + tag.id(CopyTagsDialogComponent.autoId--); + tag.isnew(true); + tag.tag_type(this.curTagType.id); + tag.label(this.curTag.label); + tag.owner(this.auth.user().ws_ou()); + tag.pub('t'); + } else { + tag = this.tagMap[this.curTag.id]; + } + + this.newTags.push(tag); + } + + createNewTags(): Promise { + let promise = Promise.resolve(); + + this.newTags.forEach(tag => { + if (!tag.isnew()) { return; } + + promise = promise.then(_ => { + return this.pcrud.create(tag).toPromise().then(id => { + console.log('create returned ', id); + tag.id(id); + }); + }); + }); + + return promise; + } + + applyChanges() { + + if (this.inPlaceMode) { + this.close(this.newTags); + return; + } + + // Create the tags then map them to our copies + + let promise = this.createNewTags(); + + this.newTags.forEach(tag => { + this.copies.forEach(copy => { + + if (copy.tags() && copy.tags().filter( + t => t.tag_type() === tag.id()).length > 0) { + return; // map already exists + } + + promise = promise.then(_ => { + const map = this.idl.create('acptcm'); + map.isnew(true); + map.copy(copy.id()); + map.tag(tag.id()); + return this.pcrud.create(map).toPromise(); + }); + }) + }); + + promise.then(_ => { + this.successMsg.current().then(msg => this.toast.success(msg)); + this.close(this.newTags.length > 0); + }); + } + + /* + applyChanges() { + const tags = this.copy.copy_tags().filter(a => a.ischanged()); + if (tags.length === 0) { return; } + this.pcrud.update(tags).toPromise().then( + ok => this.successMsg.current().then(msg => this.toast.success(msg)), + err => this.errorMsg.current().then(msg => this.toast.danger(msg)) + ); + } + */ +} + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts index b33863bf4c..b84a4abacc 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.module.ts @@ -4,6 +4,7 @@ import {HoldingsService} from './holdings.service'; import {MarkDamagedDialogComponent} from './mark-damaged-dialog.component'; import {MarkMissingDialogComponent} from './mark-missing-dialog.component'; import {CopyAlertsDialogComponent} from './copy-alerts-dialog.component'; +import {CopyTagsDialogComponent} from './copy-tags-dialog.component'; import {ReplaceBarcodeDialogComponent} from './replace-barcode-dialog.component'; import {DeleteHoldingDialogComponent} from './delete-volcopy-dialog.component'; import {ConjoinedItemsDialogComponent} from './conjoined-items-dialog.component'; @@ -16,6 +17,7 @@ import {BatchItemAttrComponent} from './batch-item-attr.component'; MarkDamagedDialogComponent, MarkMissingDialogComponent, CopyAlertsDialogComponent, + CopyTagsDialogComponent, ReplaceBarcodeDialogComponent, DeleteHoldingDialogComponent, ConjoinedItemsDialogComponent, @@ -30,6 +32,7 @@ import {BatchItemAttrComponent} from './batch-item-attr.component'; MarkDamagedDialogComponent, MarkMissingDialogComponent, CopyAlertsDialogComponent, + CopyTagsDialogComponent, ReplaceBarcodeDialogComponent, DeleteHoldingDialogComponent, ConjoinedItemsDialogComponent,