From a665f3e9c45c3037d386aee64813b36ec90d5b20 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Tue, 7 Dec 2021 12:41:18 -0500 Subject: [PATCH] LP#1942220: teach LI title sort to ignore common articles Also move a couple convenience methods related to LI sorting to LineitemService Signed-off-by: Galen Charlton --- .../staff/acq/lineitem/lineitem-list.component.ts | 38 +++++++++++++++++- .../src/app/staff/acq/lineitem/lineitem.service.ts | 42 ++++++++++++++++++++ .../eg2/src/app/staff/acq/po/print.component.ts | 46 ++++------------------ 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem-list.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem-list.component.ts index 90c63e4875..3a0bc03c34 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem-list.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem-list.component.ts @@ -257,7 +257,7 @@ export class LineitemListComponent implements OnInit { this.lineitemIds = []; const searchTerms = {}; - const opts = { id_list: true, limit: 10000 }; + const opts = { limit: 10000 }; if (this.picklistId) { Object.assign(searchTerms, { jub: [ { picklist: this.picklistId } ] }); @@ -276,6 +276,21 @@ export class LineitemListComponent implements OnInit { } Object.assign(opts, SORT_ORDER_MAP[this.sortOrder]); + let _doingTitleSort = false; + if (this.sortOrder === 'title_asc' || + this.sortOrder === 'title_desc') { + // if we're going to sort by title, we'll need + // to actually fetch LI attributes so that we can + // do a client-side sorting pass that ignores + // articles + _doingTitleSort = true; + opts['flesh_attrs'] = true; + } else { + // not doing a title sort, so we can rely on + // the server-side sort as is + opts['id_list'] = true; + } + return this.net.request( 'open-ils.acq', 'open-ils.acq.lineitem.unified_search.atomic', @@ -285,7 +300,26 @@ export class LineitemListComponent implements OnInit { null, opts ).toPromise().then(resp => { - this.lineitemIds = resp.map(i => Number(i)); + if (_doingTitleSort) { + const sortOrder = this.sortOrder; + const liService = this.liService; + function _compareLIs(a, b) { + const direction = sortOrder.match(/_asc$/) ? 'asc' : 'desc'; + const field = sortOrder.replace(/_asc|_desc$/, ''); + + const a_val = liService.getLISortKey(a, field); + const b_val = liService.getLISortKey(b, field); + + if (direction === 'asc') { + return liService.nullableCompare(a_val, b_val); + } else { + return -liService.nullableCompare(a_val, b_val); + } + } + this.lineitemIds = resp.sort(_compareLIs).map(l => Number(l.id())); + } else { + this.lineitemIds = resp.map(i => Number(i)); + } this.pager.resultCount = resp.length; }); } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts index fd48a56a58..315a1162bc 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts @@ -11,6 +11,11 @@ import {ItemLocationService} from '@eg/share/item-location-select/item-location- const COPY_ORDER_DISPOSITIONS: 'canceled' | 'delayed' | 'received' | 'on-order' | 'pre-order' = null; export type COPY_ORDER_DISPOSITION = typeof COPY_ORDER_DISPOSITIONS; +const ORDER_IDENT_ATTRS = [ + 'isbn', + 'issn', + 'upc' +]; export interface BatchLineitemStruct { id: number; @@ -372,5 +377,42 @@ export class LineitemService { return 'on-order'; } else { return 'pre-order'; } } + + // convenience function for sorting values + nullableCompare(a_val: any, b_val: any): number { + return a_val === b_val ? 0 : + a_val === null ? 1 : + b_val === null ? -1 : + a_val > b_val ? 1 : + a_val < b_val ? -1 : 0; + } + + // Given a line item, get its sort key + getLISortKey(li: IdlObject, field: string): any { + let vals = []; + switch (field) { + case 'li_id': + return li.id(); + break; + case 'title': + vals = li.attributes().filter(x => x.attr_name() === 'title'); + return vals.length ? vals[0].attr_value().replace(/^(a|an|the|el|la) /i, '') : null; + break; + case 'author': + vals = li.attributes().filter(x => x.attr_name() === 'author'); + return vals.length ? vals[0].attr_value() : null; + break; + case 'publisher': + vals = li.attributes().filter(x => x.attr_name() === 'publisher'); + return vals.length ? vals[0].attr_value() : null; + break; + case 'order_ident': + vals = li.attributes().filter(x => ORDER_IDENT_ATTRS.includes(x.attr_name())); + return vals.length ? vals[0].attr_value() : null; + break; + default: + return li.id(); + } + } } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/po/print.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/po/print.component.ts index bbcfbd8240..d0f0ea3e1b 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/po/print.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/po/print.component.ts @@ -12,6 +12,7 @@ import {OrgService} from '@eg/core/org.service'; import {PrintService} from '@eg/share/print/print.service'; import {BroadcastService} from '@eg/share/util/broadcast.service'; import {PoService} from './po.service'; +import {LineitemService} from '../lineitem/lineitem.service'; const DEFAULT_SORT_ORDER = 'li_id_asc'; const SORT_ORDERS = [ @@ -53,6 +54,7 @@ export class PrintComponent implements OnInit, AfterViewInit { private store: ServerStoreService, private pcrud: PcrudService, private poService: PoService, + private liService: LineitemService, private broadcaster: BroadcastService, private printer: PrintService) { } @@ -94,56 +96,22 @@ export class PrintComponent implements OnInit, AfterViewInit { .then(_ => this.initDone = true); } - _getLISortKey(li: IdlObject, field: string): any { - let vals = []; - switch (field) { - case 'li_id': - return li.id(); - break; - case 'title': - vals = li.attributes().filter(x => x.attr_name() === 'title'); - return vals.length ? vals[0].attr_value() : null; - break; - case 'author': - vals = li.attributes().filter(x => x.attr_name() === 'author'); - return vals.length ? vals[0].attr_value() : null; - break; - case 'publisher': - vals = li.attributes().filter(x => x.attr_name() === 'publisher'); - return vals.length ? vals[0].attr_value() : null; - break; - case 'order_ident': - vals = li.attributes().filter(x => ORDER_IDENT_ATTRS.includes(x.attr_name())); - return vals.length ? vals[0].attr_value() : null; - break; - default: - return li.id(); - } - } - sortLineItems(): Promise { return this.store.getItem('acq.lineitem.sort_order').then(sortOrder => { if (!sortOrder || !SORT_ORDERS.includes(sortOrder)) { sortOrder = DEFAULT_SORT_ORDER; } - const _getLISortKey = this._getLISortKey; - function nullableCompare(a_val: any, b_val: any): number { - return a_val === b_val ? 0 : - a_val === null ? 1 : - b_val === null ? -1 : - a_val > b_val ? 1 : - a_val < b_val ? -1 : 0; - } + const liService = this.liService; function _compareLIs(a, b) { const direction = sortOrder.match(/_asc$/) ? 'asc' : 'desc'; const field = sortOrder.replace(/_asc|_desc$/, ''); - const a_val = _getLISortKey(a, field); - const b_val = _getLISortKey(b, field); + const a_val = liService.getLISortKey(a, field); + const b_val = liService.getLISortKey(b, field); if (direction === 'asc') { - return nullableCompare(a_val, b_val); + return liService.nullableCompare(a_val, b_val); } else { - return -nullableCompare(a_val, b_val); + return -liService.nullableCompare(a_val, b_val); } } this.po.lineitems().sort(_compareLIs); -- 2.11.0