From 17326c28fa024b9512346185a42448fadc018b70 Mon Sep 17 00:00:00 2001 From: Tiffany Little Date: Wed, 10 May 2023 10:05:46 -0400 Subject: [PATCH] LP1988993 Patron requests Angularizes the acquisitions patron requests interface. Adds a new table for request-specific cancel reasons. Signed-off-by: Tiffany Little --- Open-ILS/examples/fm_IDL.xml | 36 +- .../staff/acq/lineitem/brief-record.component.ts | 49 ++ .../acq/lineitem/cancel-dialog.component.html | 15 +- .../acq/lineitem/lineitem-list.component.html | 3 +- .../src/app/staff/acq/lineitem/lineitem.module.ts | 1 + .../eg2/src/app/staff/acq/po/create.component.ts | 10 +- .../staff/acq/requests/acq-requests.component.html | 163 ++++++ .../staff/acq/requests/acq-requests.component.ts | 607 +++++++++++++++++++++ .../app/staff/acq/requests/acq-requests.module.ts | 41 ++ .../requests/create-select-dialog.component.html | 56 ++ .../acq/requests/create-select-dialog.component.ts | 39 ++ .../acq/requests/hold-pref-dialog.component.html | 27 + .../acq/requests/hold-pref-dialog.component.ts | 16 + .../link-to-lineitem-dialog.component.html | 38 ++ .../requests/link-to-lineitem-dialog.component.ts | 72 +++ .../src/app/staff/acq/requests/requests.service.ts | 99 ++++ .../src/app/staff/acq/requests/routing.module.ts | 20 + .../src/eg2/src/app/staff/acq/routing.module.ts | 4 + .../acq/search/lineitem-results.component.html | 6 +- .../staff/acq/search/lineitem-results.component.ts | 28 + .../src/app/staff/admin/acq/admin-acq.module.ts | 4 +- .../admin/acq/cancel-reasons-admin.component.html | 24 + .../admin/acq/cancel-reasons-admin.component.ts | 7 + .../eg2/src/app/staff/admin/acq/routing.module.ts | 7 + Open-ILS/src/eg2/src/app/staff/nav.component.html | 5 + Open-ILS/src/eg2/src/app/staff/nav.component.ts | 7 + .../perlmods/lib/OpenILS/Application/Acq/Order.pm | 31 ++ Open-ILS/src/sql/Pg/200.schema.acq.sql | 27 +- Open-ILS/src/sql/Pg/950.data.seed-values.sql | 12 + .../XXXX.data.enable_acq_patron_requests.sql | 16 + .../Pg/upgrade/XXXX.schema.acq-usr-req-status.sql | 9 + .../XXXX.schema.acq.request_cancelreasons.sql | 17 + 32 files changed, 1458 insertions(+), 38 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/requests.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/acq/requests/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.ts create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.enable_acq_patron_requests.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq-usr-req-status.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.request_cancelreasons.sql diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 949ed2b1c2..d9bf7a38d3 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -9242,6 +9242,7 @@ SELECT usr, + @@ -9249,7 +9250,7 @@ SELECT usr, - + @@ -9269,6 +9270,26 @@ SELECT usr, + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - diff --git a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts index 3d5912e310..5b142000ad 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts @@ -27,6 +27,10 @@ export class BriefRecordComponent implements OnInit { targetPicklist: number; targetPo: number; + targetRequestId: number; + targetRequest: IdlObject; + title: string; + author: string; attrs: IdlObject[] = []; values: {[attr: string]: string} = {}; @@ -54,6 +58,34 @@ export class BriefRecordComponent implements OnInit { this.pcrud.retrieveAll('acqlimad') .subscribe(attr => this.attrs.push(attr)); + + this.route.queryParamMap.subscribe((params: ParamMap) => { + let val; + if (val = params.get('1')) { + this.values[1] = val; + } + if (val = params.get('2')) { + this.values[2] = val; + } + if (val = params.get('requestId')) { + this.targetRequestId = val; + } + }) + + if (this.targetRequestId) { + console.log('targetreqid'+this.targetRequestId); + this.pcrud.retrieve('aur',this.targetRequestId).toPromise().then( + resp => this.targetRequest = resp); + } + + console.log('title is'+this.title); + // this.route.queryParamMap.subscribe((params: ParamMap) => + // {this.title = params.get('title'); + // console.log('title is now'+this.title); + // }) + + + console.log('attrs is'+this.attrs); } compile(): string { @@ -62,6 +94,7 @@ export class BriefRecordComponent implements OnInit { this.attrs.forEach(attr => { const value = this.values[attr.id()]; + if (value === undefined) { return; } const expr = attr.xpath(); @@ -154,6 +187,22 @@ export class BriefRecordComponent implements OnInit { this.liService.activateStateChange.emit(); + if (this.targetRequestId) { + // Brief record originated from a patron request. Link the new lineitem + // to its patron request. + + this.targetRequest.lineitem(liId); + this.targetRequest.status('Pending'); + + this.pcrud.update(this.targetRequest).toPromise().then( + ok => {}, + err => console.error('im an error') + ); + } + + + + if (this.selectedPl) { // Brief record was added to a picklist that is not // currently focused in the UI. Jump to it. diff --git a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/cancel-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/cancel-dialog.component.html index b56ee6371b..b51d224ba6 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/lineitem/cancel-dialog.component.html +++ b/Open-ILS/src/eg2/src/app/staff/acq/lineitem/cancel-dialog.component.html @@ -6,6 +6,11 @@ + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.ts new file mode 100644 index 0000000000..53c5f87a57 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.ts @@ -0,0 +1,607 @@ +import {Component, OnInit, AfterViewInit, ViewChild,Input, ChangeDetectorRef, OnDestroy} from '@angular/core'; +import {Pager} from '@eg/share/util/pager'; +import {Location} from '@angular/common'; +import {FormatService} from '@eg/core/format.service'; +import {EventService} from '@eg/core/event.service'; +import {tap} from 'rxjs/operators'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {PrintService} from '@eg/share/print/print.service'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component'; +import {AuthService} from '@eg/core/auth.service'; +import {StoreService} from '@eg/core/store.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {GridFilterControlComponent} from '@eg/share/grid/grid-filter-control.component'; +import {GridToolbarCheckboxComponent} from '@eg/share/grid/grid-toolbar-checkbox.component'; +import {ToastService} from '@eg/share/toast/toast.service'; +import {GridDataSource, GridColumn, GridContext, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {OrgService} from '@eg/core/org.service'; +import {PermService} from '@eg/core/perm.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component'; +import {StringComponent} from '@eg/share/string/string.component'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; +import {HoldPrefDialogComponent} from './hold-pref-dialog.component'; +import {LinktoLineitemDialogComponent} from './link-to-lineitem-dialog.component'; +import {PromptDialogComponent} from '@eg/share/dialog/prompt.component'; +import {PatronSearchDialogComponent} from '@eg/staff/share/patron/search-dialog.component'; +import {RequestsService} from './requests.service'; +import {CancelDialogComponent} from '@eg/staff/acq/lineitem/cancel-dialog.component'; +import {CreateSelectDialogComponent} from './create-select-dialog.component'; + + + +@Component({ + templateUrl: './acq-requests.component.html' + }) + +export class AcqRequestsComponent extends AdminPageComponent implements OnInit { + + dataSource: GridDataSource; + @ViewChild('acqRequestsGrid', { static: true }) acqRequestsGrid: GridComponent; + @ViewChild('gridFilterControlComponent', {static: true}) gridFilter: GridFilterControlComponent; + @ViewChild('context', {static: true}) context: GridContext; + @ViewChild('gridCol', {static: true}) gridCol: GridColumn; + @ViewChild('editDialog', { static: true}) editDialog: FmRecordEditorComponent; + @ViewChild('viewRequestDialog', { static: true }) viewRequestDialog: FmRecordEditorComponent; + @ViewChild('createString', { static: false }) createString: StringComponent; + @ViewChild('createErrString', { static: false }) createErrString: StringComponent; + @ViewChild('successString', { static: true }) successString: StringComponent; + @ViewChild('updateFailedString', { static: false }) updateFailedString: StringComponent; + @ViewChild('errorString', { static: true }) errorString: StringComponent; + @ViewChild('leaveConfirm', { static: true }) leaveConfirm: ConfirmDialogComponent; + @ViewChild('showCanceledRequestsCheckbox', { static: true }) showCanceledRequestsCheckbox: GridToolbarCheckboxComponent; + @ViewChild('noPicklistAlert', {static: true}) noPicklistAlert: AlertDialogComponent; + @ViewChild('holdPreferenceDialog', {static: true}) holdPreferenceDialog: HoldPrefDialogComponent; + @ViewChild('noActionableRequests', {static: true}) noActionableRequests: AlertDialogComponent; + @ViewChild('noMarkedLineitemAlert', {static: true}) noMarkedLineitemAlert: AlertDialogComponent; + @ViewChild('linktoLineitemDialog', {static: true}) linktoLineitemDialog : LinktoLineitemDialogComponent; + @ViewChild('patronSearch', {static: true}) patronSearch: PatronSearchDialogComponent; + @ViewChild('cancelDialog', {static: true}) cancelDialog: CancelDialogComponent; + @ViewChild('editHoldInfo', {static: true}) editHoldInfo: FmRecordEditorComponent; + @ViewChild('createSelectDialog', {static: true}) createSelectDialog: CreateSelectDialogComponent; + @ViewChild('plNameExists') plNameExists: AlertDialogComponent; + + + cellTextGenerator: GridCellTextGenerator; + notOneSelectedRow: (rows: IdlObject[]) => boolean; + @Input() sortField: string; + @Input() usrId: number; + @Input() lineitemId: number; + classLabel: string; + idlClass = 'aur'; + requestsEnabledSetting = false; + liId: number; + pref: boolean; + eligibleRequests: IdlObject[] = []; + nonEligibleRequests: IdlObject[]; + patron: IdlObject; + patronId: Number; + showCanceledRequests: Boolean; + //statusCol: GridColumn; + markedLineitemId: number; + contextOrgId: number; + request: IdlObject; + addtopl = false; + addtopo = false; + pofromreq = false; + selectedPl: Number; + plName: string; + + @Input() dialogSize: 'sm' | 'lg' = 'lg'; + + constructor( + route: ActivatedRoute, + ngLocation: Location, + format: FormatService, + idl: IdlService, + org: OrgService, + auth: AuthService, + pcrud: PcrudService, + perm: PermService, + toast: ToastService, + private ngLocation2: Location, + private org2: OrgService, + private route2: ActivatedRoute, + private router: Router, + private localStore: StoreService, + private store: ServerStoreService, + private evt: EventService, + private net: NetService, + private ReqService: RequestsService, + ) { + super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast); + this.dataSource = new GridDataSource(); + } + + +ngOnInit() { + this.contextOrg = this.org2.get(this.contextOrgId); + + this.cellTextGenerator = { + usr: row => row.usr().card().barcode(), + jub: row => row.lineitem().state() + }; + this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1); + this.showCanceledRequests = this.localStore.getLocalItem('eg.acq.requests.show_canceled'); + this.markedLineitemId = this.localStore.getLocalItem('eg.acq.requests.marked_lineitem') || null; + this.requestsEnabled; + + + + + + this.defaultNewRecord = this.idl.create('aur'); + + this.cellTextGenerator = { + user: row => row.usr().card().barcode(), + lineitem: row => row.lineitem().state() + } + + + this.dataSource.getRows = (pager: Pager, sort: any[]) => { + const orderBy: any = {}; + if (sort.length) { + // Sort specified from grid + orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir; + } else if (this.sortField) { + // Default sort field + orderBy[this.idlClass] = this.sortField; + } + + const searchOps = { + offset: pager.offset, + limit: pager.limit, + order_by: orderBy, + flesh: 3, + flesh_fields: { + aur: ['usr','status'], + au: ['card'], + jub: ['lineitem','state'] + } + }; + + const reqOps = { + fleshSelectors: true, + }; + + if (!this.contextOrg && !Object.keys(this.dataSource.filters).length) { + // No org filter -- fetch all rows + return this.pcrud.retrieveAll( + this.idlClass, searchOps, reqOps); + } + + const search: any = new Array(); + const orgFilter: any = {}; + + if (this.orgField && (this.searchOrgs || this.contextOrg)) { + orgFilter[this.orgField] = + this.searchOrgs.orgIds || [this.contextOrg.id()]; + search.push(orgFilter); + } + + + if (this.showCanceledRequests === false) { + search.push({cancel_reason: null}); + console.log('evaluated as true'); + } + + + this.route2.queryParamMap.subscribe((params: ParamMap) => { + let val; + if (val = params.get('lineitem')) { + search.push({lineitem: val}); + } + if (val = params.get('usr')) { + search.push({usr: val}); + } + }) + + Object.keys(this.dataSource.filters).forEach(key => { + Object.keys(this.dataSource.filters[key]).forEach(key2 => { + search.push(this.dataSource.filters[key][key2]); + }); + }); + + return this.pcrud.search( + this.idlClass, search, searchOps, reqOps); +} + +super.ngOnInit(); + +this.classLabel = this.idlClassDef.label; +this.includeOrgDescendants = true; + + } + + showEditDialog(request: IdlObject): Promise { + this.editDialog.mode = 'update'; + this.editDialog.recordId = request['id'](); + + this.patronId = request['usr']().id(); + + this.pcrud.retrieve('au',this.patronId, + {flesh: 1, + flesh_fields: + {au: ['card','home_ou']}}). + subscribe(id => this.patron = id); + + return new Promise((resolve, reject) => { + this.editDialog.open({size: this.dialogSize}).subscribe( + result => { + // this.successString.current() + // .then(str => this.toast.success(str)); + this.acqRequestsGrid.reload(); + // ); + resolve(result); + }, + error => { + // this.updateFailedString.current() + // .then(str => this.toast.danger(str)); + reject(error); + } + ); + }); + } + + editSelected(existingRequests: IdlObject[]) { + + //Only requests in New status are eligible to be fully edited + const rs = existingRequests.filter(r => + r.status() === 'New' + ); + + if (rs.length === 0) { + this.noActionableRequests.open(); + return; + } + // Edit each IDL thing one at a time + const editOneThing = (r: IdlObject) => { + if (!r) { return; } + + this.showEditDialog(r).then( + () => editOneThing(rs.shift())); + }; + + editOneThing(rs.shift()); + } + + createRequest() { + this.editDialog.mode = 'create'; + const request = this.idl.create('aur'); + this.editDialog.record = request; + this.editDialog.recordId = null; + this.patronId = null; + request.pickup_lib(this.auth.user().ws_ou()); + this.editDialog.open().subscribe( + result => { + this.createString.current() + .then(str => this.toast.success(str)); + this.acqRequestsGrid.reload(); + }, + error => { + this.createErrString.current() + .then(str => this.toast.danger(str)); + } + ); + } + + modifyHoldInfo(row: IdlObject): Promise { + //only eligible to change if status is New or Pending + const rs = row.filter(r => + ['New','Pending'].includes(r.status()) + ); + + if (rs.length === 0) { + this.noActionableRequests.open(); + return; + } + this.editHoldInfo.mode = 'update'; + this.editHoldInfo.recordId = row[0].id(); + return new Promise((resolve, reject) => { + this.editHoldInfo.open({size: this.dialogSize}).subscribe( + result => { + this.successString.current() + .then(str => this.toast.success(str)); + this.acqRequestsGrid.reload(); + resolve(result); + }, + error => { + this.updateFailedString.current() + .then(str => this.toast.danger(str)); + reject(error); + } + ); + }); + } + + toggleShowCanceled(apply: boolean) { + this.showCanceledRequests = apply; + this.localStore.setLocalItem('eg.acq.requests.show_canceled', this.showCanceledRequests); + this.acqRequestsGrid.reload(); + } + + viewRequest(rows: IdlObject[]) { + this.editDialog.mode = 'view'; + this.editDialog.recordId = rows[0].id(); + this.patronId = rows[0].usr(); + this.editDialog.open({ size: 'lg' }); + } + + requestsEnabled() { + return this.store.getItemBatch(['acq.user_requests']) + .then(settings => { + this.requestsEnabledSetting = settings['acq.user_requests']; + }) + } + + cancelSelected(rows: IdlObject[]) { + + const ids = this.ReqService.determineEligibility(rows,'okToCancel')[0]; + + if (ids.length === 0) { + this.noActionableRequests.open(); + return; + } + + this.cancelDialog.open().subscribe(reason => { + console.log(reason); + if (!reason) { return; } + + this.net.request('open-ils.acq', + 'open-ils.acq.user_request.cancel.batch', + this.auth.token(), ids, reason + ).toPromise().then(resp => { + this.acqRequestsGrid.reload(); + }); + }); + } + + viewPicklist(rows: IdlObject[]) { + this.liId = rows[0].lineitem(); + if (!this.liId) { + this.noPicklistAlert.open(); + return; + } + const url = this.ngLocation2.prepareExternalUrl('/staff/acq/search/selectionlists?f=jub:id&val1='+this.liId); + window.open(url, '_blank'); + } + + addToPicklist(): Promise { + this.createSelectDialog.addtopo = false; + this.createSelectDialog.addtopl = true; + console.log('were in addtopicklist and value of addtopl is'+this.addtopl); + return this.createSelectDialog.open() + .pipe(tap(resp => { + if (!resp) { + return Promise.resolve(); + } + else { + this.plName = resp; + console.log('plName is'+this.plName); + } + + })).toPromise().then(create => { + if (!create) {return Promise.resolve();} + if (Number(create) > 0) { + return Promise.resolve(create); + } + else return this.ReqService.createPicklist(create) + .then( + id => id, + err => { + const evt = this.evt.parse(err); + return Promise.reject('Picklist create failed'); + } + ) + }); + } + + addToPo(): Promise { + this.createSelectDialog.addtopl = false; + this.createSelectDialog.addtopo = true; + return this.createSelectDialog.open() + .pipe(tap(resp => { + if (!resp) { + console.log('no response'); + return Promise.resolve(); + } + if (Number(resp)> 0) { + console.log('weve got a number resp'); + return Promise.resolve(resp); + } + if (resp = 'createNew') { + console.log('createnew is coming through'); + return Promise.resolve('createNew'); + } + else { + console.log('weve got the third option'); + } + + })).toPromise(); + } + + requestForAdd(row: IdlObject, type: string) { + //TODO determine eligibility, only New requests should be eligible + + const request = row[0]; + const params = { + "requestId": request.id(), + "1": request.title(), + "2": request.author() + }; + + console.log('type is'+type); + if (type === 'picklist') { + console.log('were heading to addtopl'); + console.log('value of addtopl is'+this.addtopl); + + this.addToPicklist().then(pl => { + console.log('pl is '+pl); + if (!pl) { + return;} + else { + const url = `staff/acq/picklist/${pl}/brief-record`; + this.router.navigate([url], {queryParams: params}); + } + }); + } + if (type === 'po') { + console.log('were heading to addtopo'); + console.log('value of addtopo is'+this.addtopo); + + this.addToPo().then(po => { + + if (po === 'createNew') { + const url = `staff/acq/po/create`; + this.router.navigate([url], {queryParams: params}); + } + console.log('po is'+po); + if (Number(po) > 0) { + const url = `staff/acq/po/${po}/brief-record`; + this.router.navigate([url], {queryParams: params}); + } + }); + } + else return; + } + + clearCompleted() { + this.pcrud.search('aur', { + '-or': [ + {status: 'Fulfilled'}, + {'-and': [ + {status: 'Received'}, + {hold: 'f'} + ]}]} + ).toPromise().then(data => { + if (!data) {return; } + + this.eligibleRequests = data; + console.log('eligiblereq'+this.eligibleRequests); + + return this.net.request('open-ils.acq', + 'open-ils.acq.clear_completed_user_requests', + this.auth.token(), this.eligibleRequests + ).toPromise().then(resp => { + console.log('resp is'+resp); + this.acqRequestsGrid.reload(); + }) + }); + } + // } + + setHoldPref(rows: IdlObject[]) { + + //Only requests that are New or Pending status are eligible to have their hold preference changed + + this.eligibleRequests = []; + + const eIds = this.ReqService.determineEligibility(rows,'newOrPending')[0]; + const neIds = this.ReqService.determineEligibility(rows,'newOrPending')[1]; + + if (eIds.length === 0 ) { + this.noActionableRequests.open(); + return; + } + + this.holdPreferenceDialog.eligibleReqs = eIds; + this.holdPreferenceDialog.nonEligibleReqs = neIds; + + + this.holdPreferenceDialog.open().subscribe( + newPref => { + if (newPref === true) { + this.net.request('open-ils.acq', + 'open-ils.acq.user_request.set_yes_hold.batch', + this.auth.token(), eIds + ).toPromise().then(resp => { + this.acqRequestsGrid.reload(); + }); + } + + if (newPref === false) { + this.net.request('open-ils.acq', + 'open-ils.acq.user_request.set_no_hold.batch', + this.auth.token(), eIds + ).toPromise().then(resp => { + this.acqRequestsGrid.reload(); + }); + } + }); + } + + linkExistingLineitem(rows: IdlObject[]) { + // We don't have anything marked right now + if (this.markedLineitemId === null) { + console.log('no lineitems marked'); + this.noMarkedLineitemAlert.open(); + return; + }; + + //Right now only requests that are New or Pending status are eligible to link to existing lineitems + //It's still eligible even if it already has a lineitem, we'll just replace it + + const eIds = this.ReqService.determineEligibility(rows,'newOrPending')[0]; + const neIds = this.ReqService.determineEligibility(rows,'newOrPending')[1]; + + //If none meet the criteria, open the dialog to alert the user nothing was found + if (eIds.length === 0 ) { + this.noActionableRequests.open(); + return; + } + + this.linktoLineitemDialog.eligibleReqs = eIds; + this.linktoLineitemDialog.nonEligibleReqs = neIds; + this.linktoLineitemDialog.lineitem = this.markedLineitemId; + + + this.linktoLineitemDialog.open({size: 'lg'}).subscribe( + markedLI => { + const reqs: IdlObject[] = []; + rows.forEach(pq => { + if (rows[0].id) { + this.request = rows[0].id}; + pq.lineitem(this.markedLineitemId); + pq.status('Pending'); + console.log('pq value is '+pq); + pq.ischanged(true); + reqs.push(pq); + }); + return this.pcrud.autoApply(reqs).toPromise().then( + ok => {console.log('done'), + this.acqRequestsGrid.reload(), + console.log('we just refreshed') + }, + err => console.error(err) + );}); + } + + clearMarkedLineitem() { + this.markedLineitemId = null; + console.log('id is'+this.markedLineitemId); + this.localStore.setLocalItem('eg.acq.requests.marked_lineitem', this.markedLineitemId); + } + + openAcqliaSearch(rows: IdlObject[]) { + console.log(rows[0].id); + const title = rows[0].title(); + const params = {f: 'acqlia:1',op:'__fuzzy',val1: title}; + this.router.navigate(['/staff/acq/search/lineitems'], {queryParams: params}); + } + + searchPatrons() { + this.patronSearch.open({size: 'xl'}).toPromise().then( + patrons => { + if (!patrons || patrons.length === 0) {return; } + this.patron = patrons[0]; + this.patronId = this.patron.id(); + console.log(this.patron.id()); + } + ) + } + + } diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.module.ts new file mode 100644 index 0000000000..ec8d79ea21 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.module.ts @@ -0,0 +1,41 @@ +import {NgModule} from '@angular/core'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {AcqRequestsRoutingModule} from './routing.module'; +import {AcqRequestsComponent} from './acq-requests.component'; +import {OrgFamilySelectModule} from '@eg/share/org-family-select/org-family-select.module'; +import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module'; +import {HoldPrefDialogComponent} from './hold-pref-dialog.component'; +import {LinktoLineitemDialogComponent} from './link-to-lineitem-dialog.component'; +import {PatronModule} from '@eg/staff/share/patron/patron.module'; +import {RequestsService} from './requests.service'; +import {LineitemModule} from '@eg/staff/acq/lineitem/lineitem.module'; +import {CreateSelectDialogComponent} from './create-select-dialog.component'; + + +@NgModule({ + declarations: [ + AcqRequestsComponent, + HoldPrefDialogComponent, + LinktoLineitemDialogComponent, + CreateSelectDialogComponent + ], + imports: [ + StaffCommonModule, + OrgFamilySelectModule, + FmRecordEditorModule, + AcqRequestsRoutingModule, + PatronModule, + LineitemModule, + ], + exports: [ + HoldPrefDialogComponent, + LinktoLineitemDialogComponent, + CreateSelectDialogComponent + ], + providers: [ + RequestsService + ], +}) + +export class AcqRequestsModule { +} diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.html new file mode 100644 index 0000000000..170e7b88c7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.html @@ -0,0 +1,56 @@ + + + + +
+ + +
+
\ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.ts new file mode 100644 index 0000000000..32a40e4409 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.ts @@ -0,0 +1,39 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit, OnDestroy} from '@angular/core'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; +import {Router} from '@angular/router'; + + +@Component({ + selector: 'eg-acq-create-select-dialog', + templateUrl: './create-select-dialog.component.html' +}) + +export class CreateSelectDialogComponent extends DialogComponent implements OnDestroy { + @Input() addtopl: boolean; + @Input() addtopo: boolean; + pofromreq = false; + pl: ComboboxEntry; + po: ComboboxEntry; + + constructor( + private modal: NgbModal, + private router: Router + ) + + { super(modal); } + + ngOnInIt() { + console.log('addtopl in dialog is'+this.addtopl); + console.log('addtopo in dialog is'+this.addtopo); + } + + ngOnDestroy() { + this.addtopl = false; + this.addtopo = false; + } +} \ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.html new file mode 100644 index 0000000000..5b870dc99e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.html @@ -0,0 +1,27 @@ + +
+ + + +
+
\ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.ts new file mode 100644 index 0000000000..b66b643e30 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.ts @@ -0,0 +1,16 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; + +@Component({ + selector: 'eg-acq-hold-pref-dialog', + templateUrl: './hold-pref-dialog.component.html' +}) + +export class HoldPrefDialogComponent extends DialogComponent { + @Input() eligibleReqs: number[]; + @Input() nonEligibleReqs: number[]; + patronrequests: IdlObject; + constructor(private modal: NgbModal) { super(modal); } +} \ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.html new file mode 100644 index 0000000000..4aec5cf187 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.html @@ -0,0 +1,38 @@ + +
+ + + +
+
\ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.ts new file mode 100644 index 0000000000..c960bb1f99 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.ts @@ -0,0 +1,72 @@ +import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core'; +import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; +import {DialogComponent} from '@eg/share/dialog/dialog.component'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {Observable, from} from 'rxjs'; +import {tap, map, switchMap} from 'rxjs/operators'; +import {StoreService} from '@eg/core/store.service'; +import {LineitemService, FleshCacheParams} from '@eg/staff/acq/lineitem/lineitem.service'; +import {AuthService} from '@eg/core/auth.service'; + + + +@Component({ + selector: 'eg-acq-req-link-dialog', + templateUrl: './link-to-lineitem-dialog.component.html' +}) + +export class LinktoLineitemDialogComponent extends DialogComponent { + @Input() eligibleReqs: number[]; + @Input() nonEligibleReqs: number[]; + @Input() lineitem: number; + + lineitemFleshed: IdlObject; + + + constructor( + private modal: NgbModal, + private pcrud: PcrudService, + private localStore: StoreService, + private net: NetService, + private auth: AuthService, + private liService: LineitemService + ) + { super(modal); } + +//ngOnInIt doesn't work for this, stop trying + + open(args?: NgbModalOptions): Observable { + if (!args) { + args = { }; + } + + const obs = from(this.getFleshedLineItem()); + + return obs.pipe(switchMap(_ => super.open(args))); + } + + + getFleshedLineItem() { + return this.net.request( + 'open-ils.acq','open-ils.acq.lineitem.retrieve', + this.auth.token(),this.lineitem, { + flesh_attrs: true, + flesh_po: true, + flesh_pl: true + } + ).toPromise() + .then(li => this.lineitemFleshed = li); + } + + getTitle(li: IdlObject): string { + if (!li) {return '';} + return this.liService.getFirstAttributeValue(li, 'title'); + } + + getAuthor(li: IdlObject): string { + if (!li) {return '';} + return this.liService.getFirstAttributeValue(li, 'author'); + } +} \ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/requests.service.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/requests.service.ts new file mode 100644 index 0000000000..b74fa1da7d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/requests.service.ts @@ -0,0 +1,99 @@ +import {Injectable} from '@angular/core'; +import {Observable} from 'rxjs'; +import {Subject} from 'rxjs'; +import {map, defaultIfEmpty} from 'rxjs/operators'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; + + + +@Injectable() +export class RequestsService { + + eligibleRequests: IdlObject[]; + nonEligibleRequests: IdlObject[]; + + constructor( + private idl: IdlService, + private pcrud: PcrudService, + private auth: AuthService, + private net: NetService + ) { + } + +batchUpdate(list: IdlObject | IdlObject[]): Observable { + return this.pcrud.autoApply(list); +} + +createPicklist(name: string): Promise { + + return this.pcrud.search('acqpl', + { owner: this.auth.user().id(), name: name }, null, { idlist: true } + ).toPromise().then(existing => { + return { existing: existing, name: name }; + }) + .then(info => { + + + // if (info.existing) { + // Alert the user the requested name is already in + // use and reopen the create dialog. + // this.plNameExists.open().toPromise().then(_ => this.createSelectDialog.open()); + // return; + // } + console.log('we got to the creation of the pl'); + const pl = this.idl.create('acqpl'); + pl.name(name); + pl.owner(this.auth.user().id()); + + return this.net.request( + 'open-ils.acq', + 'open-ils.acq.picklist.create', this.auth.token(), pl + ).toPromise(); + + }).then(plId => { + if (!plId) { return; } + console.log('pl id is ' + plId); + return Promise.resolve(plId); + }); + +} + +determineEligibility(requests: IdlObject[], statuses: String) { + + this.eligibleRequests = []; + this.nonEligibleRequests = []; + + if (statuses === 'newOrPending') { + + requests.forEach(r => { + + if (r.status() === 'New' || r.status() === 'Pending') { + this.eligibleRequests.push(r); + } + else {this.nonEligibleRequests.push(r)}; + }); + } + + if (statuses === 'newOnly') { + this.eligibleRequests = requests.filter( + r => r.status() === 'New' + ); + } + + if (statuses === 'okToCancel') { + this.eligibleRequests = requests.filter( + r => !['Canceled','Fulfilled'].includes(r.status()) + ); + } + const eIds = this.eligibleRequests.map(x => Number(x.id())); + const neIds = this.nonEligibleRequests.map(x => Number(x.id())); + + console.log('length of eids is'+eIds.length); + + return ([eIds,neIds]); +} + +} \ No newline at end of file diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/requests/routing.module.ts new file mode 100644 index 0000000000..1a091aa7dd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/acq/requests/routing.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; +import {AcqRequestsComponent} from './acq-requests.component'; +import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component'; +import {BriefRecordComponent} from '../lineitem/brief-record.component'; + +const routes: Routes = [{ + path: '', + component: AcqRequestsComponent +}, { + path: 'brief-record', + component: BriefRecordComponent +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) + +export class AcqRequestsRoutingModule {} 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 index 926a680a62..aac13088d1 100644 --- a/Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts @@ -23,6 +23,10 @@ const routes: Routes = [{ path: 'related', loadChildren: () => import('./related/related.module').then(m => m.RelatedModule) +}, { + path: 'requests', + loadChildren: () => + import('./requests/acq-requests.module').then(m => m.AcqRequestsModule) }]; @NgModule({ 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 index b0c8dd1058..79ac52d5a3 100644 --- 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 @@ -170,8 +170,12 @@ (onClick)="deleteLineitems($event)" [disableOnRows]="noSelectedRows"> + (onClick)="exportSingleAttributeList($event)" [disableOnRows]="noSelectedRows"> + + 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 index 6a30b788d8..321f55474b 100644 --- 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 @@ -4,6 +4,7 @@ import {map, concatMap} 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 {StoreService} from '@eg/core/store.service'; import {NetService} from '@eg/core/net.service'; import {AuthService} from '@eg/core/auth.service'; import {GridComponent} from '@eg/share/grid/grid.component'; @@ -31,6 +32,8 @@ export class LineitemResultsComponent implements OnInit { @Input() initialSearchTerms: AcqSearchTerm[] = []; + markedLI: IdlObject; + gridSource: GridDataSource; @ViewChild('acqSearchForm', { static: true}) acqSearchForm: AcqSearchFormComponent; @ViewChild('acqSearchLineitemsGrid', { static: true }) lineitemResultsGrid: GridComponent; @@ -54,11 +57,13 @@ export class LineitemResultsComponent implements OnInit { noSelectedRows: (rows: IdlObject[]) => boolean; + notOneSelectedRow: (rows: IdlObject[]) => boolean; cellTextGenerator: GridCellTextGenerator; constructor( private router: Router, private route: ActivatedRoute, + private localStore: StoreService, private net: NetService, private auth: AuthService, private toast: ToastService, @@ -69,6 +74,8 @@ export class LineitemResultsComponent implements OnInit { ngOnInit() { this.gridSource = this.acqSearch.getAcqSearchDataSource('lineitem'); this.noSelectedRows = (rows: IdlObject[]) => (rows.length === 0); + this.markedLI = this.localStore.getLocalItem('eg.acq.requests.marked_lineitem') || null; + this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1); this.cellTextGenerator = { id: row => row.id(), title: row => { @@ -390,5 +397,26 @@ export class LineitemResultsComponent implements OnInit { this.lineitemResultsGrid.reload(); }); } + + markLIforRequest(row: IdlObject) { + //Must be in a pre-order state + const li = row.filter( + r => ['new','selector-ready','order-ready','approved'].includes(r.state()) + ); + if (li.length === 0) { + // this.noActionableLIs.open(); + console.log('well open the nonactionablelis here'); + return; + }; + this.markedLI = row[0].id(); + console.log(this.markedLI); + this.localStore.setLocalItem('eg.acq.requests.marked_lineitem',this.markedLI); + this.router.navigate(['/staff/acq/requests']); + } + + clearMark() { + this.markedLI = null; + this.localStore.setLocalItem('eg.acq.requests.marked_lineitem', this.markedLI); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts index 067ea39db7..5dd2e04ac7 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts @@ -4,11 +4,13 @@ import {AdminAcqRoutingModule} from './routing.module'; import {AdminCommonModule} from '@eg/staff/admin/common.module'; import {AdminAcqSplashComponent} from './admin-acq-splash.component'; import {ClaimingAdminComponent} from './claiming-admin.component'; +import {CancelReasonsComponent} from './cancel-reasons-admin.component'; @NgModule({ declarations: [ AdminAcqSplashComponent, - ClaimingAdminComponent + ClaimingAdminComponent, + CancelReasonsComponent ], imports: [ AdminCommonModule, diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.html new file mode 100644 index 0000000000..ff7b0c4d8d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.html @@ -0,0 +1,24 @@ + + + + + + +
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.ts new file mode 100644 index 0000000000..739c7b6cc9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.ts @@ -0,0 +1,7 @@ +import {Component} from '@angular/core'; + +@Component({ + templateUrl: './cancel-reasons-admin.component.html' +}) +export class CancelReasonsComponent { +} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts index 254f650d65..fadbab7726 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts @@ -3,6 +3,7 @@ import {RouterModule, Routes} from '@angular/router'; import {AdminAcqSplashComponent} from './admin-acq-splash.component'; import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component'; import {ClaimingAdminComponent} from './claiming-admin.component'; +import {CancelReasonsComponent} from './cancel-reasons-admin.component' const routes: Routes = [{ path: 'splash', @@ -17,6 +18,12 @@ const routes: Routes = [{ readonlyFields: 'last_activity' }] }, { + path: 'cancel_reason', + component: CancelReasonsComponent +}, { + path: 'request_cancel_reason', + redirectTo: 'cancel_reason' // from legacy auto-generated admin page +}, { path: 'claiming', component: ClaimingAdminComponent }, { 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 d7db8bd391..52ee872787 100644 --- a/Open-ILS/src/eg2/src/app/staff/nav.component.html +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.html @@ -337,6 +337,11 @@ Patron Requests + + + Patron Requests (NEW) + diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.ts b/Open-ILS/src/eg2/src/app/staff/nav.component.ts index d4201a84a2..1a3142336c 100644 --- a/Open-ILS/src/eg2/src/app/staff/nav.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.ts @@ -30,6 +30,7 @@ export class StaffNavComponent implements OnInit, OnDestroy { curbsideEnabled: boolean; showAngularCirc = false; maxRecentPatrons: number = 1; + requestsEnabled: boolean; @ViewChild('navOpChange', {static: false}) opChange: OpChangeComponent; permFailedSub: Subscription; @@ -73,6 +74,7 @@ export class StaffNavComponent implements OnInit, OnDestroy { .then(settings => this.curbsideEnabled = Boolean(settings['circ.curbside'])); +<<<<<<< HEAD this.org.settings('ui.staff.max_recent_patrons') .then(settings => this.maxRecentPatrons = settings['ui.staff.max_recent_patrons'] ?? 1) @@ -90,6 +92,11 @@ export class StaffNavComponent implements OnInit, OnDestroy { return false; } }).then(enable => this.showAngularCirc = enable); +======= + this.org.settings('acq.user_requests') + .then(settings => this.requestsEnabled = + Boolean(settings['acq.user_requests'])); +>>>>>>> 90cbbc1937... Edits eg2 nav menu to only show requests if yaous enabled } // Wire up our op-change component as the general purpose diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm index 81c2b6c58b..9147b4fb42 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm @@ -263,8 +263,10 @@ sub promote_lineitem_holds { for my $request ( @$requests ) { $request->eg_bib( $li->eg_bib_id ); + $request->status('Ordered, Hold Not Placed'); $mgr->editor->update_acq_user_request( $request ) or return 0; + next unless ($U->is_true( $request->hold )); my $existing_hold = $mgr->editor->search_action_hold_request( @@ -321,6 +323,8 @@ sub promote_lineitem_holds { } $mgr->editor->create_action_hold_request( $hold ) or return 0; + $request->status('Ordered, Hold Placed'); + $mgr->editor->update_acq_user_request( $request ) or return 0; } return $li; @@ -646,6 +650,15 @@ sub receive_lineitem { $li->state('received'); $li = update_lineitem($mgr, $li) or return 0; + + my $requests = $mgr->editor->search_acq_user_request({lineitem => $li_id}) or return 0; + + for my $request ( @$requests ) { + $request->status('Received'); + $mgr->editor->update_acq_user_request($request) or return 0; + next; + }; + $mgr->post_process( sub { create_lineitem_status_events($mgr, $li_id, 'aur.received'); }); my $po; @@ -674,6 +687,23 @@ sub rollback_receive_lineitem { $mgr->add_li; $li->state('on-order'); + + my $requests = $mgr->editor->search_acq_user_request({lineitem => $li_id}) or return 0; + + for my $request (@$requests ) { + + my $existing_hold = $mgr->editor->search_action_hold_request( + {acq_request => $request->id})->[0]; + + if ($existing_hold) { + $request->status('Ordered, Hold Placed'); + } else { + $request->status('Ordered, Hold Not Placed') + } + $mgr->editor->update_acq_user_request($request) or return 0; + next; + }; + return update_lineitem($mgr, $li); } @@ -3777,6 +3807,7 @@ sub update_user_request { if ( $cancel_reason ) { $aur_obj->cancel_reason( $cancel_reason ); $aur_obj->cancel_time( 'now' ); + $aur_obj->status('Canceled'); $e->update_acq_user_request($aur_obj) or return $e->die_event; create_user_request_events( $e, [ $aur_obj ], 'aur.rejected' ); } else { diff --git a/Open-ILS/src/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql index 3195feec61..dc0786d783 100644 --- a/Open-ILS/src/sql/Pg/200.schema.acq.sql +++ b/Open-ILS/src/sql/Pg/200.schema.acq.sql @@ -950,23 +950,13 @@ CREATE TABLE acq.user_request_type ( label TEXT NOT NULL UNIQUE -- i18n-ize ); -CREATE TABLE acq.user_request_status_type ( - id SERIAL PRIMARY KEY - ,label TEXT -); - -INSERT INTO acq.user_request_status_type (id,label) VALUES - (0,oils_i18n_gettext(0,'Error','aurst','label')) - ,(1,oils_i18n_gettext(1,'New','aurst','label')) - ,(2,oils_i18n_gettext(2,'Pending','aurst','label')) - ,(3,oils_i18n_gettext(3,'Ordered, Hold Not Placed','aurst','label')) - ,(4,oils_i18n_gettext(4,'Ordered, Hold Placed','aurst','label')) - ,(5,oils_i18n_gettext(5,'Received','aurst','label')) - ,(6,oils_i18n_gettext(6,'Fulfilled','aurst','label')) - ,(7,oils_i18n_gettext(7,'Canceled','aurst','label')) -; -SELECT SETVAL('acq.user_request_status_type_id_seq'::TEXT, 100); +CREATE TABLE acq.request_cancel_reason ( + id SERIAL PRIMARY KEY, + org_unit INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, + description TEXT +); CREATE TABLE acq.user_request ( id SERIAL PRIMARY KEY, @@ -996,9 +986,10 @@ CREATE TABLE acq.user_request ( pubdate TEXT, mentioned TEXT, other_info TEXT, - cancel_reason INT REFERENCES acq.cancel_reason( id ) + cancel_reason INT REFERENCES acq.request_cancel_reason( id ) DEFERRABLE INITIALLY DEFERRED, - cancel_time TIMESTAMPTZ + cancel_time TIMESTAMPTZ, + status TEXT NOT NULL DEFAULT 'New' ); ALTER TABLE action.hold_request ADD COLUMN acq_request INT REFERENCES acq.user_request (id); diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index ec54030d8b..dafb87b2cb 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -23464,3 +23464,15 @@ INSERT INTO config.org_unit_setting_type (name, label, grp, description, datatyp 'link', 'csp') ; +INSERT into config.org_unit_setting_type (name, label, grp, description, datatype) +VALUES ( + 'acq.user_requests', + oils_i18n_gettext('acq.user_requests', + 'Enable patron requests', + 'coust','label'), + 'acq', + oils_i18n_gettext('acq.user_requests', + 'When set to TRUE, enables the patron requests feature of Acquisitions in the staff interface.', + 'coust','description'), + 'bool' +); diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.enable_acq_patron_requests.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.enable_acq_patron_requests.sql new file mode 100644 index 0000000000..9edeebd3ce --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.enable_acq_patron_requests.sql @@ -0,0 +1,16 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check(XXXX, :eg_version); + +INSERT into config.org_unit_setting_type (name, label, grp, description, datatype) +VALUES ( + 'acq.user_requests', + oils_i18n_gettext('acq.user_requests', + 'Enable patron requests', + 'coust','label'), + 'acq', + oils_i18n_gettext('acq.user_requests', + 'When set to TRUE, enables the patron requests feature of Acquisitions in the staff interface.', + 'coust','description'), + 'bool' +); \ No newline at end of file diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq-usr-req-status.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq-usr-req-status.sql new file mode 100644 index 0000000000..d0885faff4 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq-usr-req-status.sql @@ -0,0 +1,9 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + + +ALTER TABLE acq.user_request ADD COLUMN status TEXT NOT NULL DEFAULT 'New'; + + +COMMIT; \ No newline at end of file diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.request_cancelreasons.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.request_cancelreasons.sql new file mode 100644 index 0000000000..ccb6f16b9e --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.request_cancelreasons.sql @@ -0,0 +1,17 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +CREATE TABLE acq.request_cancel_reason ( + id SERIAL PRIMARY KEY, + org_unit INT NOT NULL REFERENCES actor.org_unit (id), + label TEXT NOT NULL, + description TEXT +); + +ALTER TABLE acq.user_request + ADD CONSTRAINT user_request_cancel_reason_fkey + FOREIGN KEY (id) references acq.request_cancel_reason (id) + DEFERRABLE INITIALLY DEFERRED; + +COMMIT; \ No newline at end of file -- 2.11.0