From 2482c3c84a141131679d3b7a3236933827542d59 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Tue, 22 Oct 2019 15:29:40 -0400 Subject: [PATCH] Angular Acquistions Search Angular app + linking AngularJS navigation elements to it Signed-off-by: Galen Charlton --- .../src/eg2/src/app/staff/acq/routing.module.ts | 15 ++ .../staff/acq/search/acq-search-form.component.css | 5 + .../acq/search/acq-search-form.component.html | 142 +++++++++++ .../staff/acq/search/acq-search-form.component.ts | 173 ++++++++++++++ .../app/staff/acq/search/acq-search.component.html | 24 ++ .../app/staff/acq/search/acq-search.component.ts | 102 ++++++++ .../src/app/staff/acq/search/acq-search.module.ts | 35 +++ .../src/app/staff/acq/search/acq-search.service.ts | 259 +++++++++++++++++++++ .../acq/search/invoice-results.component.html | 45 ++++ .../staff/acq/search/invoice-results.component.ts | 82 +++++++ .../acq/search/lineitem-results.component.html | 71 ++++++ .../staff/acq/search/lineitem-results.component.ts | 44 ++++ .../search/picklist-clone-dialog.component.html | 27 +++ .../acq/search/picklist-clone-dialog.component.ts | 75 ++++++ .../search/picklist-create-dialog.component.html | 27 +++ .../acq/search/picklist-create-dialog.component.ts | 70 ++++++ .../search/picklist-delete-dialog.component.html | 24 ++ .../acq/search/picklist-delete-dialog.component.ts | 75 ++++++ .../search/picklist-merge-dialog.component.html | 32 +++ .../acq/search/picklist-merge-dialog.component.ts | 71 ++++++ .../acq/search/picklist-results.component.html | 59 +++++ .../staff/acq/search/picklist-results.component.ts | 121 ++++++++++ .../search/purchase-order-results.component.html | 32 +++ .../acq/search/purchase-order-results.component.ts | 44 ++++ .../eg2/src/app/staff/acq/search/routing.module.ts | 20 ++ Open-ILS/src/eg2/src/app/staff/nav.component.html | 11 +- Open-ILS/src/eg2/src/app/staff/nav.component.ts | 8 + Open-ILS/src/eg2/src/app/staff/routing.module.ts | 3 + Open-ILS/src/eg2/src/styles.css | 19 ++ Open-ILS/src/templates/staff/navbar.tt2 | 8 +- .../web/js/ui/default/staff/services/navbar.js | 2 + 31 files changed, 1717 insertions(+), 8 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/search/routing.module.ts diff --git a/Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts new file mode 100644 index 0000000000..1305bd0ea2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts @@ -0,0 +1,15 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +const routes: Routes = [ + { path: 'search', + loadChildren: './search/acq-search.module#AcqSearchModule' + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) + +export class AcqRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.css b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.css new file mode 100644 index 0000000000..8842d11421 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.css @@ -0,0 +1,5 @@ +#acq-search-form { + border-radius: 0px 0px 7px 7px; + background-color: rgb(247, 247, 247); + box-shadow: 1px 2px 3px -1px rgba(0, 0, 0, .2); +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.html new file mode 100644 index 0000000000..53a6152fc9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.html @@ -0,0 +1,142 @@ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + and + + + + + + +
+
+
+ + +
+
+
+
+ +
+
+
+ + +
+
+ +
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.ts new file mode 100644 index 0000000000..785e0a6f35 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search-form.component.ts @@ -0,0 +1,173 @@ +import {Component, OnInit, AfterViewInit, Input, Output, EventEmitter} from '@angular/core'; +import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {Router, ActivatedRoute} from '@angular/router'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {AcqSearchTerm, AcqSearch} from './acq-search.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; + +@Component({ + selector: 'eg-acq-search-form', + styleUrls: ['acq-search-form.component.css'], + templateUrl: './acq-search-form.component.html' +}) + +export class AcqSearchFormComponent implements OnInit, AfterViewInit { + + @Input() initialSearchTerms: AcqSearchTerm[] = []; + @Input() defaultSearchSetting = ''; + @Input() runImmediatelySetting = ''; + @Input() searchTypeLabel = ''; + + @Output() searchSubmitted = new EventEmitter(); + + showForm = true; + + hints = ['jub', 'acqpl', 'acqpo', 'acqinv', 'acqlid']; + availableSearchFields = {}; + searchTermDatatypes = {}; + searchFieldLinkedClasses = {}; + validSearchTypes = ['lineitems', 'purchaseorders', 'invoices', 'selectionlists']; + defaultSearchType = 'lineitems'; + searchConjunction = 'all'; + runImmediately = false; + + searchTerms: AcqSearchTerm[] = []; + + constructor( + private router: Router, + private route: ActivatedRoute, + private pcrud: PcrudService, + private store: ServerStoreService, + private idl: IdlService, + ) {} + + ngOnInit() { + const self = this; + + this.store.getItem(this.runImmediatelySetting).then(val => { + this.runImmediately = val; + + this.hints.forEach( + function(hint) { + const o = {}; + o['__label'] = self.idl.classes[hint].label; + o['__fields'] = []; + self.idl.classes[hint].fields.forEach( + function(field) { + if (!field.virtual) { + o['__fields'].push(field.name); + o[field.name] = { + label: field.label, + datatype: field.datatype + }; + self.searchTermDatatypes[hint + ':' + field.name] = field.datatype; + if (field.datatype === 'link') { + self.searchFieldLinkedClasses[hint + ':' + field.name] = field.class; + } + } + } + ); + self.availableSearchFields[hint] = o; + } + ); + + this.hints.push('acqlia'); + this.availableSearchFields['acqlia'] = {'__label': this.idl.classes.acqlia.label, '__fields': []}; + this.pcrud.retrieveAll('acqliad', {'order_by': {'acqliad': 'id'}}) + .subscribe(liad => { + this.availableSearchFields['acqlia']['__fields'].push('' + liad.id()); + this.availableSearchFields['acqlia'][liad.id()] = { + label: liad.description(), + datatype: 'text' + }; + this.searchTermDatatypes['acqlia:' + liad.id()] = 'text'; + }); + + if (this.initialSearchTerms.length > 0) { + this.searchTerms = JSON.parse(JSON.stringify(this.initialSearchTerms)); // deep copy + this.submitSearch(); // if we've been passed an initial search, e.g., via a URL, assume + // we want the results immediately regardless of the workstation + // setting + } else { + this.store.getItem(this.defaultSearchSetting).then( + defaultSearch => { + if (defaultSearch) { + this.searchTerms = JSON.parse(JSON.stringify(defaultSearch.terms)); + this.searchConjunction = defaultSearch.conjunction; + } else { + this.addSearchTerm(); + } + if (this.runImmediately) { + this.submitSearch(); + } + } + ); + } + }); + } + + ngAfterViewInit() {} + + addSearchTerm() { + this.searchTerms.push({ field: '', op: '', value1: '', value2: '' }); + } + delSearchTerm(index: number) { + if (this.searchTerms.length < 2) { + this.clearSearchTerm(this.searchTerms[0]); + } else { + this.searchTerms.splice(index, 1); + } + } + clearSearchTerm(term: AcqSearchTerm) { + term.value1 = ''; + term.value2 = ''; + term.is_date = false; + + if (term.field.startsWith('acqlia:') && term.op === '') { + // default operator for line item attributes should be "contains" + term.op = '__fuzzy'; + } else if (this.searchTermDatatypes[term.field] !== 'text' && term.op.endsWith('__fuzzy')) { + // avoid trying to use the "contains" operator for non-text fields + term.op = ''; + } + } + // conditionally clear the search term after changing + // to selected search operators + clearSearchTermValueAfterOpChange(term: AcqSearchTerm) { + if (term.op === '__age') { + term.value1 = ''; + term.value2 = ''; + } + } + + setOrgUnitSearchValue(org: IdlObject, term: AcqSearchTerm) { + if (org == null) { + term.value1 = ''; + } else { + term.value1 = org.id(); + } + } + + submitSearch() { + // tossing setTimeout here to ensure that the + // grid data source is fully initialized + setTimeout(() => { + this.searchSubmitted.emit({ + terms: this.searchTerms, + conjunction: this.searchConjunction + }); + }); + } + + saveSearchAsDefault() { + return this.store.setItem(this.defaultSearchSetting, { + terms: this.searchTerms, + conjunction: this.searchConjunction + }); + } + saveRunImmediately() { + return this.store.setItem(this.runImmediatelySetting, this.runImmediately); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.html new file mode 100644 index 0000000000..d5d5a4d4a9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.html @@ -0,0 +1,24 @@ + + + + +
+
+ + + + + + + + + + + + + + +
+
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.ts new file mode 100644 index 0000000000..7eb21d84c2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.component.ts @@ -0,0 +1,102 @@ +import {Component, OnInit, AfterViewInit, ViewChild, ViewChildren, QueryList} from '@angular/core'; +import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {Router, ActivatedRoute, ParamMap, NavigationEnd} from '@angular/router'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {AcqSearchTerm} from './acq-search.service'; +import {LineitemResultsComponent} from './lineitem-results.component'; +import {PurchaseOrderResultsComponent} from './purchase-order-results.component'; +import {InvoiceResultsComponent} from './invoice-results.component'; +import {PicklistResultsComponent} from './picklist-results.component'; + +@Component({ + templateUrl: './acq-search.component.html' +}) + +export class AcqSearchComponent implements OnInit, AfterViewInit { + + searchType = ''; + validSearchTypes = ['lineitems', 'purchaseorders', 'invoices', 'selectionlists']; + defaultSearchType = 'lineitems'; + + urlSearchTerms: AcqSearchTerm[] = []; + + onTabChange: ($event: NgbTabChangeEvent) => void; + @ViewChild('acqSearchTabs', { static: true }) tabs: NgbTabset; + @ViewChildren(LineitemResultsComponent) liResults: QueryList; + @ViewChildren(PurchaseOrderResultsComponent) poResults: QueryList; + @ViewChildren(InvoiceResultsComponent) invResults: QueryList; + @ViewChildren(PicklistResultsComponent) plResults: QueryList; + + constructor( + private router: Router, + private route: ActivatedRoute, + private pcrud: PcrudService, + private idl: IdlService, + ) { + this.route.queryParamMap.subscribe((params: ParamMap) => { + this.urlSearchTerms = []; + const fields = params.getAll('f'); + const ops = params.getAll('op'); + const values1 = params.getAll('val1'); + const values2 = params.getAll('val2'); + fields.forEach((f, idx) => { + const term: AcqSearchTerm = { + field: f, + op: '', + value1: '', + value2: '' + }; + if (idx < ops.length) { + term.op = ops[idx]; + } + if (idx < values1.length) { + term.value1 = values1[idx]; + if (term.value1 === 'null') { + // convert the string 'null' to a true + // null value, mostly for the benefit of the + // open invoices navigation link + term.value1 = null; + } + } + if (idx < values2.length) { + term.value2 = values2[idx]; + } + this.urlSearchTerms.push(term); + this.ngOnInit(); // TODO: probably overkill + }); + }); + this.router.events.subscribe(routeEvent => { + if (routeEvent instanceof NavigationEnd) { + this.ngOnInit(); // TODO: probably overkill + } + }); + } + + ngOnInit() { + const self = this; + + const searchTypeParam = this.route.snapshot.paramMap.get('searchtype'); + + if (searchTypeParam) { + if (this.validSearchTypes.includes(searchTypeParam)) { + this.searchType = searchTypeParam; + } else { + this.searchType = this.defaultSearchType; + this.router.navigate(['/staff', 'acq', 'search', this.searchType]); + } + } + + this.onTabChange = ($event) => { + if (this.validSearchTypes.includes($event.nextId)) { + this.searchType = $event.nextId; + this.urlSearchTerms = []; + this.router.navigate(['/staff', 'acq', 'search', $event.nextId]); + } + }; + } + + ngAfterViewInit() {} + +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.module.ts new file mode 100644 index 0000000000..4dc22517f1 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.module.ts @@ -0,0 +1,35 @@ +import {NgModule} from '@angular/core'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {AcqSearchRoutingModule} from './routing.module'; +import {AcqSearchComponent} from './acq-search.component'; +import {AcqSearchFormComponent} from './acq-search-form.component'; +import {LineitemResultsComponent} from './lineitem-results.component'; +import {PurchaseOrderResultsComponent} from './purchase-order-results.component'; +import {InvoiceResultsComponent} from './invoice-results.component'; +import {PicklistResultsComponent} from './picklist-results.component'; +import {PicklistCreateDialogComponent} from './picklist-create-dialog.component'; +import {PicklistCloneDialogComponent} from './picklist-clone-dialog.component'; +import {PicklistDeleteDialogComponent} from './picklist-delete-dialog.component'; +import {PicklistMergeDialogComponent} from './picklist-merge-dialog.component'; + +@NgModule({ + declarations: [ + AcqSearchComponent, + AcqSearchFormComponent, + LineitemResultsComponent, + PurchaseOrderResultsComponent, + InvoiceResultsComponent, + PicklistResultsComponent, + PicklistCreateDialogComponent, + PicklistCloneDialogComponent, + PicklistDeleteDialogComponent, + PicklistMergeDialogComponent + ], + imports: [ + StaffCommonModule, + AcqSearchRoutingModule + ] +}) + +export class AcqSearchModule { +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.service.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.service.ts new file mode 100644 index 0000000000..d32c121331 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/acq-search.service.ts @@ -0,0 +1,259 @@ +import {Injectable} from '@angular/core'; +import {empty, throwError} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {Pager} from '@eg/share/util/pager'; +import {IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; + +const defaultSearch = { + lineitem: { + jub: [{ + id: '0', + __gte: true + }] + }, + purchase_order: { + acqpo: [{ + id: '0', + __gte: true + }] + }, + picklist: { + acqpl: [{ + id: '0', + __gte: true + }] + }, + invoice: { + acqinv: [{ + id: '0', + __gte: true + }] + }, +}; + +const searchOptions = { + lineitem: { + flesh_attrs: true, + flesh_cancel_reason: true, + flesh_notes: true, + flesh_provider: true, + flesh_claim_policy: true, + flesh_queued_record: true, + }, + purchase_order: { + no_flesh_cancel_reason: true, + flesh_provider: true, + flesh_owner: false, + flesh_creator: false, + flesh_editor: false + }, + picklist: { + flesh_lineitem_count: true, + flesh_owner: true, + flesh_creator: false, + flesh_editor: false + }, + invoice: { + no_flesh_misc: true, + flesh_provider: true // and shipper, which is also a provider + } +}; + +const operatorMap = { + '!=': '__not', + '>': '__gte', + '>=': '__gte', + '<=': '__lte', + '<': '__lte', + 'startswith': '__starts', + 'endswith': '__ends', + 'like': '__fuzzy', +}; + +export interface AcqSearchTerm { + field: string; + op: string; + value1: string; + value2: string; + is_date?: boolean; +} + +export interface AcqSearch { + terms: AcqSearchTerm[]; + conjunction: string; +} + +@Injectable() +export class AcqSearchService { + + _terms: AcqSearchTerm[] = []; + _conjunction = 'all'; + attrDefs: {[code: string]: IdlObject}; + firstRun = true; + + constructor( + private net: NetService, + private evt: EventService, + private auth: AuthService, + private pcrud: PcrudService + ) { + this.attrDefs = {}; + this.firstRun = true; + } + + fetchAttrDefs(): Promise { + if (Object.keys(this.attrDefs).length) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + this.pcrud.retrieveAll('acqliad', {}, + {atomic: true} + ).subscribe(list => { + list.forEach(acqliad => { + this.attrDefs[acqliad.code()] = acqliad; + }); + resolve(); + }); + }); + } + + setSearch(search: AcqSearch) { + this._terms = search.terms; + this._conjunction = search.conjunction; + this.firstRun = false; + } + + generateAcqSearch(searchType, filters): any { + const andTerms = JSON.parse(JSON.stringify(defaultSearch[searchType])); // deep copy + const orTerms = {}; + const coreRecType = Object.keys(defaultSearch[searchType])[0]; + + // handle supplied search terms + this._terms.forEach(term => { + if (term.value1 === '') { + return; + } + const searchTerm: Object = {}; + const recType = term.field.split(':')[0]; + const searchField = term.field.split(':')[1]; + if (term.op === '__between') { + searchTerm[searchField] = [term.value1, term.value2]; + } else { + searchTerm[searchField] = term.value1; + } + if (term.op !== '') { + searchTerm[term.op] = true; + } + if (term.is_date) { + searchTerm['__castdate'] = true; + } + if (this._conjunction === 'any') { + if (!(recType in orTerms)) { + orTerms[recType] = []; + } + orTerms[recType].push(searchTerm); + } else { + if (!(recType in andTerms)) { + andTerms[recType] = []; + } + andTerms[recType].push(searchTerm); + } + }); + + // handle grid filters + // note that date filters coming from the grid do not need + // to worry about __castdate because the grid filter supplies + // both the start and end times + const observables = []; + Object.keys(filters).forEach(filterField => { + filters[filterField].forEach(condition => { + const searchTerm: Object = {}; + let filterOp = '='; + let filterVal = ''; + if (Object.keys(condition).some(x => x === '-not')) { + filterOp = Object.keys(condition['-not'][filterField])[0]; + filterVal = condition['-not'][filterField][filterOp]; + searchTerm['__not'] = true; + } else { + filterOp = Object.keys(condition[filterField])[0]; + filterVal = condition[filterField][filterOp]; + if (filterOp === 'like' && filterVal.length > 1) { + if (filterVal[0] === '%' && filterVal[filterVal.length - 1] === '%') { + filterVal = filterVal.slice(1, filterVal.length - 1); + } else if (filterVal[filterVal.length - 1] === '%') { + filterVal = filterVal.slice(0, filterVal.length - 1); + filterOp = 'startswith'; + } else if (filterVal[0] === '%') { + filterVal = filterVal.slice(1); + filterOp = 'endswith'; + } + } + } + + if (filterOp in operatorMap) { + searchTerm[operatorMap[filterOp]] = true; + } + if ((['title', 'author'].indexOf(filterField) > -1) && + (filterField in this.attrDefs)) { + if (!('acqlia' in andTerms)) { + andTerms['acqlia'] = []; + } + searchTerm[this.attrDefs[filterField].id()] = filterVal; + andTerms['acqlia'].push(searchTerm); + } else { + searchTerm[filterField] = filterVal; + andTerms[coreRecType].push(searchTerm); + } + }); + }); + return { andTerms: andTerms, orTerms: orTerms }; + } + + getAcqSearchDataSource(searchType: string): GridDataSource { + const gridSource = new GridDataSource(); + + this.fetchAttrDefs().then(() => { + gridSource.getRows = (pager: Pager) => { + + // don't do a search the very first time we + // get invoked, which is during initialization; we'll + // let components higher up the change decide whether + // to submit a search + if (this.firstRun) { + this.firstRun = false; + return empty(); + } + + const currentSearch = this.generateAcqSearch(searchType, gridSource.filters); + + const opts = { ...searchOptions[searchType] }; + opts['offset'] = pager.offset; + opts['limit'] = pager.limit; + opts['au_by_id'] = true; + return this.net.request( + 'open-ils.acq', + 'open-ils.acq.' + searchType + '.unified_search', + this.auth.token(), + currentSearch.andTerms, + currentSearch.orTerms, + null, + opts + ).pipe( + map(res => { + if (this.evt.parse(res)) { + throw throwError(res); + } else { + return res; + } + }), + ); + }; + }); + return gridSource; + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.html new file mode 100644 index 0000000000..e5823a4a14 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.html @@ -0,0 +1,45 @@ + + + + + {{invoice.inv_ident()}} + + + + + {{invoice.provider().code()}} + + + + + {{invoice.shipper().code()}} + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.ts new file mode 100644 index 0000000000..04f2d6ad0e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/invoice-results.component.ts @@ -0,0 +1,82 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {Pager} from '@eg/share/util/pager'; +import {IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {PrintService} from '@eg/share/print/print.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {AcqSearchService, AcqSearchTerm, AcqSearch} from './acq-search.service'; +import {AcqSearchFormComponent} from './acq-search-form.component'; + +@Component({ + selector: 'eg-invoice-results', + templateUrl: 'invoice-results.component.html', + providers: [AcqSearchService] +}) +export class InvoiceResultsComponent implements OnInit { + + @Input() initialSearchTerms: AcqSearchTerm[] = []; + + gridSource: GridDataSource; + @ViewChild('acqSearchInvoicesGrid', { static: true }) invoiceResultsGrid: GridComponent; + @ViewChild('printfail', { static: true }) private printfail: AlertDialogComponent; + + noSelectedRows: (rows: IdlObject[]) => boolean; + + constructor( + private router: Router, + private route: ActivatedRoute, + private printer: PrintService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private acqSearch: AcqSearchService) { + } + + ngOnInit() { + this.gridSource = this.acqSearch.getAcqSearchDataSource('invoice'); + this.noSelectedRows = (rows: IdlObject[]) => (rows.length === 0); + } + + printSelectedInvoices(rows: IdlObject[]) { + const that = this; + let html = '\n'; + this.net.request( + 'open-ils.acq', + 'open-ils.acq.invoice.print.html', + this.auth.token(), rows.map( invoice => invoice.id() ) + ).subscribe( + (res) => { + if (this.evt.parse(res)) { + console.error(res); + this.printfail.open(); + } else { + html += res.template_output().data(); + } + }, + (err) => { + console.error(err); + this.printfail.open(); + }, + () => this.printer.print({ + text: html, + printContext: 'default' + }) + ); + } + + doSearch(search: AcqSearch) { + setTimeout(() => { + this.acqSearch.setSearch(search); + this.invoiceResultsGrid.reload(); + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.html new file mode 100644 index 0000000000..d1ef61d89f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.html @@ -0,0 +1,71 @@ + + + + + {{lineitem.id()}} + + + {{lineitem.id()}} + + + + + + + {{lia.attr_value()}} + + + + + + + {{lineitem.provider().name()}} + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.ts new file mode 100644 index 0000000000..061eddb3ec --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.ts @@ -0,0 +1,44 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {Pager} from '@eg/share/util/pager'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {AcqSearchService, AcqSearchTerm, AcqSearch} from './acq-search.service'; +import {AcqSearchFormComponent} from './acq-search-form.component'; + +@Component({ + selector: 'eg-lineitem-results', + templateUrl: 'lineitem-results.component.html', + providers: [AcqSearchService] +}) +export class LineitemResultsComponent implements OnInit { + + @Input() initialSearchTerms: AcqSearchTerm[] = []; + + gridSource: GridDataSource; + @ViewChild('acqSearchLineitemsGrid', { static: true }) lineitemResultsGrid: GridComponent; + + constructor( + private router: Router, + private route: ActivatedRoute, + private net: NetService, + private auth: AuthService, + private acqSearch: AcqSearchService) { + } + + ngOnInit() { + this.gridSource = this.acqSearch.getAcqSearchDataSource('lineitem'); + } + + doSearch(search: AcqSearch) { + setTimeout(() => { + this.acqSearch.setSearch(search); + this.lineitemResultsGrid.reload(); + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.html new file mode 100644 index 0000000000..662aaca27b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.html @@ -0,0 +1,27 @@ + +
+ + + +
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.ts new file mode 100644 index 0000000000..e2b23d39af --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-clone-dialog.component.ts @@ -0,0 +1,75 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit, Renderer2} from '@angular/core'; +import {Observable, from, empty, throwError} from 'rxjs'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +@Component({ + selector: 'eg-picklist-clone-dialog', + templateUrl: './picklist-clone-dialog.component.html' +}) + +export class PicklistCloneDialogComponent + extends DialogComponent implements OnInit { + + @Input() grid: any; + selectionListName: String; + leadListName: String; + selections: IdlObject[]; + + @ViewChild('fail', { static: true }) private fail: AlertDialogComponent; + + constructor( + private renderer: Renderer2, + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + } + + update() { + this.leadListName = this.grid.context.getSelectedRows()[0].name(); + this.renderer.selectRootElement('#create-picklist-name').focus(); + this.selectionListName = 'Copy of ' + this.leadListName; + } + + cloneList() { + const picklist = this.idl.create('acqpl'); + picklist.owner(this.auth.user().id()); + picklist.name(this.selectionListName); + this.net.request( + 'open-ils.acq', + 'open-ils.acq.picklist.clone', + this.auth.token(), + this.grid.context.getSelectedRows()[0].id(), + this.selectionListName + ).subscribe( + (res) => { + if (this.evt.parse(res)) { + console.error(res); + this.fail.open(); + } else { + console.log(res); + } + }, + (err) => { + console.error(err); + this.fail.open(); + }, + () => this.close(true) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.html new file mode 100644 index 0000000000..b4ac8ad665 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.html @@ -0,0 +1,27 @@ + +
+ + + +
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.ts new file mode 100644 index 0000000000..9a5a8da030 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-create-dialog.component.ts @@ -0,0 +1,70 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit, Renderer2} from '@angular/core'; +import {Observable, from, empty, throwError} from 'rxjs'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +@Component({ + selector: 'eg-picklist-create-dialog', + templateUrl: './picklist-create-dialog.component.html' +}) + +export class PicklistCreateDialogComponent + extends DialogComponent implements OnInit { + + selectionListName: String; + + @ViewChild('fail', { static: true }) private fail: AlertDialogComponent; + + constructor( + private renderer: Renderer2, + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + this.selectionListName = ''; + } + + update() { + this.selectionListName = ''; + this.renderer.selectRootElement('#create-picklist-name').focus(); + } + + createList() { + const picklist = this.idl.create('acqpl'); + picklist.owner(this.auth.user().id()); + picklist.name(this.selectionListName); + this.net.request( + 'open-ils.acq', + 'open-ils.acq.picklist.create', + this.auth.token(), picklist + ).subscribe( + (res) => { + if (this.evt.parse(res)) { + console.error(res); + this.fail.open(); + } else { + console.log(res); + } + }, + (err) => { + console.error(err); + this.fail.open(); + }, + () => this.close(true) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.html new file mode 100644 index 0000000000..bd30a9d383 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.html @@ -0,0 +1,24 @@ + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.ts new file mode 100644 index 0000000000..401937e10a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-delete-dialog.component.ts @@ -0,0 +1,75 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {Observable, forkJoin, from, empty, throwError} from 'rxjs'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +@Component({ + selector: 'eg-picklist-delete-dialog', + templateUrl: './picklist-delete-dialog.component.html' +}) + +export class PicklistDeleteDialogComponent + extends DialogComponent implements OnInit { + + @Input() grid: any; + listNames: string[]; + + @ViewChild('fail', { static: true }) private fail: AlertDialogComponent; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + } + + update() { + this.listNames = this.grid.context.getSelectedRows().map( r => r.name() ); + } + + deleteList(list) { + return this.net.request( + 'open-ils.acq', + 'open-ils.acq.picklist.delete', + this.auth.token(), + list.id() + ); + } + + deleteLists() { + const that = this; + const observables = []; + this.grid.context.getSelectedRows().forEach(function(r) { + observables.push( that.deleteList(r) ); + }); + forkJoin(observables).subscribe( + (res) => { + if (this.evt.parse(res)) { + console.error(res); + this.fail.open(); + } else { + console.log(res); + } + }, + (err) => { + console.error(err); + this.fail.open(); + }, + () => this.close(true) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.html new file mode 100644 index 0000000000..6cdf54e72f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.html @@ -0,0 +1,32 @@ + +
+ + + +
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.ts new file mode 100644 index 0000000000..a7d7643d59 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-merge-dialog.component.ts @@ -0,0 +1,71 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {Observable, forkJoin, from, empty, throwError} from 'rxjs'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {EventService} from '@eg/core/event.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; + +@Component({ + selector: 'eg-picklist-merge-dialog', + templateUrl: './picklist-merge-dialog.component.html' +}) + +export class PicklistMergeDialogComponent + extends DialogComponent implements OnInit { + + @Input() grid: any; + listNames: string[]; + leadList: number; + selectedLists: IdlObject[]; + + @ViewChild('fail', { static: true }) private fail: AlertDialogComponent; + + constructor( + private idl: IdlService, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private modal: NgbModal + ) { + super(modal); + } + + ngOnInit() { + } + + update() { + this.selectedLists = this.grid.context.getSelectedRows(); + this.listNames = this.selectedLists.map( r => r.name() ); + } + + mergeLists() { + const that = this; + this.net.request( + 'open-ils.acq', + 'open-ils.acq.picklist.merge', + this.auth.token(), this.leadList, + this.selectedLists.map( list => list.id() ).filter(function(p) { return p !== that.leadList; }) + ).subscribe( + (res) => { + if (this.evt.parse(res)) { + console.error(res); + this.fail.open(); + } else { + console.log(res); + } + }, + (err) => { + console.error(err); + this.fail.open(); + }, + () => this.close(true) + ); + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.html new file mode 100644 index 0000000000..9994ec2178 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + {{selectionlist.name()}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.ts new file mode 100644 index 0000000000..3705ad17e9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/picklist-results.component.ts @@ -0,0 +1,121 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {Pager} from '@eg/share/util/pager'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {StringComponent} from '@eg/share/string/string.component'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PermService} from '@eg/core/perm.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {AcqSearchService, AcqSearchTerm, AcqSearch} from './acq-search.service'; +import {PicklistCreateDialogComponent} from './picklist-create-dialog.component'; +import {PicklistCloneDialogComponent} from './picklist-clone-dialog.component'; +import {PicklistDeleteDialogComponent} from './picklist-delete-dialog.component'; +import {PicklistMergeDialogComponent} from './picklist-merge-dialog.component'; +import {AcqSearchFormComponent} from './acq-search-form.component'; + +@Component({ + selector: 'eg-picklist-results', + templateUrl: 'picklist-results.component.html', + providers: [AcqSearchService] +}) +export class PicklistResultsComponent implements OnInit { + + @Input() initialSearchTerms: AcqSearchTerm[] = []; + + gridSource: GridDataSource; + @ViewChild('acqSearchPicklistsGrid', { static: true }) picklistResultsGrid: GridComponent; + @ViewChild('picklistCreateDialog', { static: true }) picklistCreateDialog: PicklistCreateDialogComponent; + @ViewChild('picklistCloneDialog', { static: true }) picklistCloneDialog: PicklistCloneDialogComponent; + @ViewChild('picklistDeleteDialog', { static: true }) picklistDeleteDialog: PicklistDeleteDialogComponent; + @ViewChild('picklistMergeDialog', { static: true }) picklistMergeDialog: PicklistMergeDialogComponent; + @ViewChild('createSelectionListString', { static: true }) createSelectionListString: StringComponent; + @ViewChild('cloneSelectionListString', { static: true }) cloneSelectionListString: StringComponent; + @ViewChild('deleteSelectionListString', { static: true }) deleteSelectionListString: StringComponent; + @ViewChild('mergeSelectionListString', { static: true }) mergeSelectionListString: StringComponent; + + permissions: {[name: string]: boolean}; + noSelectedRows: (rows: IdlObject[]) => boolean; + oneSelectedRows: (rows: IdlObject[]) => boolean; + createNotAppropriate: (rows: IdlObject[]) => boolean; + cloneNotAppropriate: (rows: IdlObject[]) => boolean; + mergeNotAppropriate: (rows: IdlObject[]) => boolean; + deleteNotAppropriate: (rows: IdlObject[]) => boolean; + + constructor( + private router: Router, + private route: ActivatedRoute, + private toast: ToastService, + private net: NetService, + private auth: AuthService, + private acqSearch: AcqSearchService, + private perm: PermService + ) { + this.permissions = {}; + } + + ngOnInit() { + this.gridSource = this.acqSearch.getAcqSearchDataSource('picklist'); + + this.perm.hasWorkPermHere(['CREATE_PICKLIST', 'UPDATE_PICKLIST', 'VIEW_PICKLIST']). + then(perms => this.permissions = perms); + + this.noSelectedRows = (rows: IdlObject[]) => (rows.length === 0); + this.oneSelectedRows = (rows: IdlObject[]) => (rows.length === 1); + this.createNotAppropriate = (rows: IdlObject[]) => (!this.permissions.CREATE_PICKLIST); + this.cloneNotAppropriate = (rows: IdlObject[]) => (!this.permissions.CREATE_PICKLIST || !this.oneSelectedRows(rows)); + this.mergeNotAppropriate = (rows: IdlObject[]) => (!this.permissions.UPDATE_PICKLIST || this.noSelectedRows(rows)); + this.deleteNotAppropriate = (rows: IdlObject[]) => (!this.permissions.UPDATE_PICKLIST || this.noSelectedRows(rows)); + } + + openCreateDialog() { + this.picklistCreateDialog.open().subscribe( + modified => { + this.createSelectionListString.current().then(msg => this.toast.success(msg)); + this.picklistResultsGrid.reload(); // FIXME - spec calls for inserted grid row and not refresh + } + ); + this.picklistCreateDialog.update(); // clear and focus the textbox + } + + openCloneDialog(rows: IdlObject[]) { + this.picklistCloneDialog.open().subscribe( + modified => { + this.cloneSelectionListString.current().then(msg => this.toast.success(msg)); + this.picklistResultsGrid.reload(); // FIXME - spec calls for inserted grid row and not refresh + } + ); + this.picklistCloneDialog.update(); // update the dialog UI with selections + } + + openDeleteDialog(rows: IdlObject[]) { + this.picklistDeleteDialog.open().subscribe( + modified => { + this.deleteSelectionListString.current().then(msg => this.toast.success(msg)); + this.picklistResultsGrid.reload(); // FIXME - spec calls for removed grid rows and not refresh + } + ); + this.picklistDeleteDialog.update(); // update the dialog UI with selections + } + + openMergeDialog(rows: IdlObject[]) { + this.picklistMergeDialog.open().subscribe( + modified => { + this.mergeSelectionListString.current().then(msg => this.toast.success(msg)); + this.picklistResultsGrid.reload(); // FIXME - spec calls for removed grid rows and not refresh + } + ); + this.picklistMergeDialog.update(); // update the dialog UI with selections + } + + doSearch(search: AcqSearch) { + setTimeout(() => { + this.acqSearch.setSearch(search); + this.picklistResultsGrid.reload(); + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.html b/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.html new file mode 100644 index 0000000000..14c6d96785 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.html @@ -0,0 +1,32 @@ + + + + + {{purchaseorder.name()}} + + + + + + {{purchaseorder.provider().code()}} + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.ts new file mode 100644 index 0000000000..75888b931d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/purchase-order-results.component.ts @@ -0,0 +1,44 @@ +import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Observable} from 'rxjs'; +import {map} from 'rxjs/operators'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {Pager} from '@eg/share/util/pager'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridDataSource} from '@eg/share/grid/grid'; +import {AcqSearchService, AcqSearchTerm, AcqSearch} from './acq-search.service'; +import {AcqSearchFormComponent} from './acq-search-form.component'; + +@Component({ + selector: 'eg-purchase-order-results', + templateUrl: 'purchase-order-results.component.html', + providers: [AcqSearchService] +}) +export class PurchaseOrderResultsComponent implements OnInit { + + @Input() initialSearchTerms: AcqSearchTerm[] = []; + + gridSource: GridDataSource; + @ViewChild('acqSearchPurchaseOrdersGrid', { static: true }) purchaseOrderResultsGrid: GridComponent; + + constructor( + private router: Router, + private route: ActivatedRoute, + private net: NetService, + private auth: AuthService, + private acqSearch: AcqSearchService) { + } + + ngOnInit() { + this.gridSource = this.acqSearch.getAcqSearchDataSource('purchase_order'); + } + + doSearch(search: AcqSearch) { + setTimeout(() => { + this.acqSearch.setSearch(search); + this.purchaseOrderResultsGrid.reload(); + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/search/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/search/routing.module.ts new file mode 100644 index 0000000000..c4a4f68e50 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/search/routing.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {AcqSearchComponent} from './acq-search.component'; + +const routes: Routes = [ + { path: '', + component: AcqSearchComponent + }, + { path: ':searchtype', + component: AcqSearchComponent + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [] +}) + +export class AcqSearchRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.html b/Open-ILS/src/eg2/src/app/staff/nav.component.html index 265368a62a..120cae1ee7 100644 --- a/Open-ILS/src/eg2/src/app/staff/nav.component.html +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.html @@ -239,13 +239,14 @@