Angularizes the acquisitions patron requests interface.
Adds a new table for request-specific cancel reasons.
Signed-off-by: Tiffany Little <tlittle@georgialibraries.org>
<field reporter:label="Other Info" name="other_info" reporter:datatype="text" />
<field reporter:label="Cancel Reason" name="cancel_reason" reporter:datatype="link" />
<field reporter:label="Cancel Date/Time" name="cancel_time" reporter:datatype="timestamp" />
+ <field reporter:label="Status" name="status" reporter:datatype="text" />
</fields>
<links>
<link field="usr" reltype="has_a" key="id" map="" class="au"/>
<link field="lineitem" reltype="has_a" key="id" map="" class="jub"/>
<link field="eg_bib" reltype="has_a" key="id" map="" class="bre"/>
<link field="request_type" reltype="has_a" key="id" map="" class="aurt"/>
- <link field="cancel_reason" reltype="has_a" key="id" map="" class="acqcr"/>
+ <link field="cancel_reason" reltype="has_a" key="id" map="" class="aurcr"/>
</links>
<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
<actions>
</permacrud>
</class>
+ <class id="aurcr" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="acq::request_cancel_reason" oils_persist:tablename="acq.request_cancel_reason" reporter:label="Patron Request Cancel Reason">
+ <fields oils_persist:primary="id" oils_persist:sequence="acq.request_cancel_reason_id_seq">
+ <field reporter:label="ID" name="id" reporter:datatype="id" reporter:selector='label'/>
+ <field reporter:label="Using Library" name="org_unit" oils_obj:required="true" reporter:datatype="org_unit"/>
+ <field reporter:label="Label" name="label" oils_obj:required="true" reporter:datatype="text" oils_persist:i18n="true"/>
+ <field reporter:label="Description" name="description" reporter:datatype="text" oils_persist:i18n="true"/>
+ </fields>
+ <links>
+ <link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
+ </links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="ADMIN_ACQ_CANCEL_CAUSE" context_field="org_unit"/>
+ <retrieve permission="STAFF_LOGIN" context_field="org_unit"/>
+ <update permission="ADMIN_ACQ_CANCEL_CAUSE" context_field="org_unit"/>
+ <delete permission="ADMIN_ACQ_CANCEL_CAUSE" context_field="org_unit"/>
+ </actions>
+ </permacrud>
+ </class>
+
<class id="aurs" controller="open-ils.cstore open-ils.reporter-store open-ils.pcrud" oils_obj:fieldmapper="acq::user_request_status" reporter:label="User Purchase Request with Status" oils_persist:readonly="true">
<oils_persist:source_definition><![CDATA[
SELECT r.*, CASE
</permacrud>
</class>
- <class id="aurst" controller="open-ils.cstore open-ils.reporter-store open-ils.pcrud" oils_obj:fieldmapper="acq::user_request_status_type" oils_persist:tablename="acq.user_request_status_type" reporter:label="Acquisition Patron Request Status Type">
- <fields oils_persist:primary="id">
- <field reporter:label="Status ID" name="id" reporter:datatype="id" reporter:selector='label'/>
- <field reporter:label="Status" name="label" reporter:datatype="text" oils_persist:i18n="true" />
- </fields>
- <links/>
- <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
- <actions>
- <retrieve/>
- </actions>
- </permacrud>
- </class>
-
<class id="acqct" controller="open-ils.cstore open-ils.reporter-store open-ils.pcrud" oils_obj:fieldmapper="acq::currency_type" oils_persist:tablename="acq.currency_type" reporter:label="Currency Type">
<fields oils_persist:primary="code">
<field reporter:label="Currency Code" name="code" reporter:datatype="text" reporter:selector='label'/>
targetPicklist: number;
targetPo: number;
+ targetRequestId: number;
+ targetRequest: IdlObject;
+ title: string;
+ author: string;
attrs: IdlObject[] = [];
values: {[attr: string]: string} = {};
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 {
this.attrs.forEach(attr => {
const value = this.values[attr.id()];
+
if (value === undefined) { return; }
const expr = attr.xpath();
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.
<h3 class="modal-title" *ngIf="recordType === 'lid'" i18n>Confirm Item Cancellation</h3>
<button type="button" class="btn-close btn-close-white"
i18n-aria-label aria-label="Close" (click)="close()"></button>
+ <h3 class="modal-title" *ngIf="recordType === 'aur'" i18n>Confirm Request Cancellation</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
</div>
<div class="modal-body">
<h4 *ngIf="recordType === 'po'" i18n>Please select a cancel reason and click "Apply" to cancel the order,
or "Exit Dialog" to exit without cancelling the line item.</h4>
<h4 *ngIf="recordType === 'lid'" i18n>Please select a cancel reason and click "Apply" to cancel the item,
or "Exit Dialog" to exit without cancelling the item.</h4>
- <eg-combobox domId="acq-cancel-dialog" name="acq-cancel-dialog"
+ <h4 *ngIf="recordType === 'aur'" i18n>Please select a cancel reason and click "Apply" to cancel the request,
+ or "Exit Dialog" to exit without cancelling the request.</h4>
+ <ng-container *ngIf="recordType === 'aur'; else elseBlock" i18n>
+ <eg-combobox domId="aur-cancel-dialog" name="aur-cancel-dialog"
+ [asyncSupportsEmptyTermClick]="true"
+ idlClass="aurcr" [(ngModel)]="cancelReason"></eg-combobox></ng-container>
+ <ng-template #elseBlock>
+ <eg-combobox domId="acq-cancel-dialog" name="acq-cancel-dialog"
[asyncSupportsEmptyTermClick]="true"
idlClass="acqcr" [(ngModel)]="cancelReason"></eg-combobox>
+ </ng-template>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" [disabled]="!cancelReason"
<span class="ms-1 me-1" i18n> | </span>
<a class="label-with-material-icon"
title="Request(s)" i18n-title
- href="/eg/staff/acq/requests/lineitem/{{li.id()}}"
+ [queryParams]="{lineitem: li.id()}"
+ routerLink="/staff/acq/requests"
target="_blank">
<span class="material-icons small me-1">help</span>
<span i18n>Request(s)</span>
LineitemWorksheetComponent
],
exports: [
+ BriefRecordComponent,
LineitemListComponent,
CancelDialogComponent,
AddToPoDialogComponent,
selector: 'eg-acq-po-create'
})
export class PoCreateComponent implements OnInit {
-
+ @Input() pofromreq: boolean;
initDone = false;
lineitems: number[] = [];
origLiCount = 0;
}
create() {
-
+ console.log('we got to the create sub');
const po = this.idl.create('acqpo');
po.ordering_agency(this.orderAgency);
po.provider(this.provider.id);
'open-ils.acq.purchase_order.create',
this.auth.token(), po, args
).toPromise().then(resp => {
+ console.log('we got a resp here');
if (resp && resp.purchase_order) {
if (this.createAssets) {
this.router.navigate(
['/staff/acq/po/' + resp.purchase_order.id() + '/create-assets']);
+ }
+ if (this.pofromreq = true) {
+ console.log('poid is'+resp.purchase_order.id());
+ this.router.navigate(
+ ['/staff/acq/po/' + resp.purchase_order.id() + '/brief-record'],{queryParamsHandling: 'merge'});
} else {
this.router.navigate(
['/staff/acq/po/' + resp.purchase_order.id()]);
--- /dev/null
+<eg-staff-banner bannerText="Acquisitions Patron Requests" i18n-bannerText>
+</eg-staff-banner>
+<eg-string #nopicklist i18n-text text="No selection list associated with this request"></eg-string>
+<eg-string #createString i18n-text text="New Request Added"></eg-string>
+<eg-string #createErrString i18n-text text="Request Failed"></eg-string>
+<eg-acq-hold-pref-dialog #holdPreferenceDialog></eg-acq-hold-pref-dialog>
+<eg-patron-search-dialog #patronSearch></eg-patron-search-dialog>
+<eg-acq-req-link-dialog #linktoLineitemDialog></eg-acq-req-link-dialog>
+<eg-acq-cancel-dialog recordType="aur" #cancelDialog></eg-acq-cancel-dialog>
+<eg-acq-create-select-dialog #createSelectDialog></eg-acq-create-select-dialog>
+
+<ng-template #patronTmpl let-row="row">
+ <a href="/eg/staff/circ/patron/{{row.usr().id()}}/checkout"
+ target="_blank">
+ {{row.usr().card().barcode()}}
+ </a>
+</ng-template>
+
+
+<!--<ng-template #viewLI let-row="row">
+ <a [queryParams]="{f: 'jub:id', val1: lineitem}"
+ routerLink="/staff/acq/search/selectionlists" target="_blank">
+ View</a>
+</ng-template><!-->
+
+<eg-confirm-dialog #leaveConfirm
+ i18n-dialogTitle i18n-dialogBody
+ dialogTitle="Unsaved Changes Warning"
+ dialogBody="There are unsaved changes. Are you sure you want to leave?">
+</eg-confirm-dialog>
+
+<eg-alert-dialog #noActionableRequests i18n-dialogBody
+dialogBody="None of the requests were eligible for this action.">
+</eg-alert-dialog>
+
+
+
+
+<eg-alert-dialog #noPicklistAlert i18n-dialogBody
+dialogBody="There is no selection list associated with this patron request."></eg-alert-dialog>
+
+<eg-alert-dialog #noMarkedLineitemAlert i18n-dialogBody
+dialogBody="There is not a line item currently marked."></eg-alert-dialog>
+
+<!--TODO: Make sure to add a confirmation toast when adding to SL. See LP1830106-->
+
+<div class="col-lg-12">
+ <div class="float-right">
+ <ng-container *ngIf="markedLineitemId">
+ <span class="text-danger" i18n><b>Lineitem ID {{markedLineitemId}} marked for hold requests</b></span>
+ </ng-container>
+ </div></div>
+
+
+<ng-container>
+ <div class="row">
+ <div class="col-lg-6">
+ <ng-container>
+ <eg-org-family-select i18n-labelText labelText="Patron Home Library"
+ [limitPerms]="viewPerms"
+ [selectedOrgId]="contextOrg.id()"
+ [(ngModel)]="searchOrgs"
+ [descendantSelectorChecked]="true"
+ (ngModelChange)="acqRequestsGrid.reload()"></eg-org-family-select>
+ </ng-container>
+ </div>
+ </div>
+ <hr/>
+</ng-container>
+
+
+<eg-grid #acqRequestsGrid
+persistKey="acq.requests"
+idlClass="aur" [dataSource]="dataSource"
+[sortable]="true"
+[filterable]="true"
+[showDeclaredFieldsOnly]="true"
+[cellTextGenerator]="cellTextGenerator">
+
+<eg-grid-toolbar-button label="Create Request" i18n-label (onClick)="createRequest()"></eg-grid-toolbar-button>
+<eg-grid-toolbar-button label="Archive Completed Requests" i18n-label (onClick)="clearCompleted()"></eg-grid-toolbar-button>
+
+<eg-grid-toolbar-checkbox label="Show Canceled Requests" i18n-label (onChange)="toggleShowCanceled($event)"
+ [initialValue]="showCanceledRequests">
+</eg-grid-toolbar-checkbox>
+
+<eg-grid-toolbar-action i18n-group group="New Requests" label="Edit Request" i18n-label (onClick)="editSelected($event)"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action label="View Request" i18n-label (onClick)="viewRequest($event)" [disableOnRows]="notOneSelectedRow"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="New Requests" label="Add Request to Selection List" i18n-label (onClick)="requestForAdd($event,'picklist')"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="New Requests" label="Add Request to PO" i18n-label (onClick)="requestForAdd($event,'po')" queryParamsHandling="merge"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action label="View Selection List" i18n-label (onClick)="viewPicklist($event)" [disableOnRows]="notOneSelectedRow"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="New or Pending Requests" label="Set Place Hold Choice (Batch)" i18n-label (onClick)="setHoldPref($event)"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="New or Pending Requests" label="Modify Hold Info" i18n-label (onClick)="modifyHoldInfo($event)"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action label="Cancel Requests" i18n-label (onClick)="cancelSelected($event)"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="Line Item Management" label="Search for matching line item" i18n-label (onClick)="openAcqliaSearch($event)" [disableOnRows]="notOneSelectedRow"></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="Line Item Management" label="Clear Marked Line item" (onClick)="clearMarkedLineitem($event)" i18n-label></eg-grid-toolbar-action>
+<eg-grid-toolbar-action i18n-group group="Line Item Management" label="Link Requests to Marked Line item" i18n-label (onClick)="linkExistingLineitem($event)"></eg-grid-toolbar-action>
+
+<eg-grid-column i18n-label label="User barcode" path="usr" [cellTemplate]="patronTmpl"></eg-grid-column>
+<eg-grid-column i18n-label label="Article title" path="article_title"></eg-grid-column>
+<eg-grid-column i18n-label label="Title" path="title"></eg-grid-column>
+<eg-grid-column i18n-label label="UPC" path="upc"></eg-grid-column>
+<eg-grid-column i18n-label label="ISxN" path="isxn"></eg-grid-column>
+<eg-grid-column i18n-label label="Pickup Lib" path="pickup_lib"></eg-grid-column>
+<eg-grid-column i18n-label label="Place Hold?" path="hold"></eg-grid-column>
+<eg-grid-column i18n-label label="Request Type" path="request_type"></eg-grid-column>
+<eg-grid-column i18n-label label="Need Before" path="need_before" [datePlusTime]="true"></eg-grid-column>
+<eg-grid-column i18n-label label="Request Date" path="request_date" [datePlusTime]="true"></eg-grid-column>
+<eg-grid-column i18n-label label="Patron Home Lib" path="usr.home_ou"></eg-grid-column>
+<eg-grid-column i18n-label label="Patron Family Name" path="usr.family_name"></eg-grid-column>
+<eg-grid-column i18n-label label="Status" path="status"></eg-grid-column>
+<eg-grid-column i18n-label label="Lineitem ID" path="lineitem"></eg-grid-column>
+<eg-grid-column i18n-label label="Cancel Time" path="cancel_time" [datePlusTime]="true" [hidden]="true"></eg-grid-column>
+<eg-grid-column i18n-label label="Selection List" path="lineitem.picklist" [hidden]="true"></eg-grid-column>
+</eg-grid>
+
+
+<eg-fm-record-editor #editDialog idlClass="aur"
+[remainOpenOnError]="true"
+fieldOrder="usr,hold,email_notify,phone_notify,pickup_lib,need_before,request_type,title,author,isxn,upc,volume,publisher,location,pubdate,article_title,article_pages,mentioned"
+[fieldOptions]="{request_type:{preloadLinkedValues:true}, usr:{customTemplate:{template:patronLookup}}}"
+requiredFields="title,pickup_lib,usr"
+[defaultNewRecord]="defaultNewRecord"
+hiddenFields="id,eg_bib,cancel_time,holdable_formats,home_ou,max_fee,lineitem,cancel_reason,status,request_date">
+</eg-fm-record-editor>
+
+<eg-fm-record-editor #editHoldInfo idlClass="aur"
+[remainOpenOnError]="true"
+readonlyFields="usr"
+fieldOrder="usr,hold,email_notify,phone_notify,pickup_lib,need_before"
+requiredFields="pickup_lib,usr"
+hiddenFields="id,request_type,title,author,isxn,upc,volume,publisher,location,pubdate,eg_bib,cancel_time,holdable_formats,home_ou,max_fee,lineitem,cancel_reason,status,request_date,article_pages,article_title,mentioned,other_info">
+</eg-fm-record-editor>
+
+
+<ng-template #patronLookup
+let-field="field" let-record="record">
+<div>
+ <div>
+ <ng-container *ngIf="patronId">
+ <h4>Selected User ID:</h4>
+ <input class="form-control" readonly
+ [ngModel]="record.usr(patronId)" name="usr"
+ (ngModelChange)="record.usr($event)">
+ <span i18n>
+ {{patron.pref_family_name() ? patron.pref_family_name() : patron.family_name()}}, {{patron.pref_first_given_name() ? patron.pref_first_given_name() :patron.first_given_name()}}<br>
+ Barcode: {{patron.card().barcode()}}<br>
+ Home Library: {{patron.home_ou().shortname()}}
+ </span>
+ </ng-container>
+
+ </div>
+ <div>
+ <ng-container *ngIf="!patronId">
+ <button class="btn btn-outline-dark btn-sm" (click)="searchPatrons()">
+ <span class="material-icons mat-icon-in-button align-middle"
+ i18n-title title="Search for Patron">search</span>
+ <span class="align-middle" i18n>Search for Patron</span>
+</button>
+</ng-container>
+</div>
+</div>
+</ng-template>
--- /dev/null
+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<any> {
+ 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<any> {
+ //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<Number> {
+ 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<any> {
+ 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());
+ }
+ )
+ }
+
+ }
--- /dev/null
+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 {
+}
--- /dev/null
+<eg-alert-dialog #plNameExists
+ i18n-dialogBody dialogBody="Selection List Name Already In Use">
+</eg-alert-dialog>
+
+<ng-template #dialogContent>
+ <form class="form-validated">
+ <div class="modal-header bg-info">
+ <h3 class="modal-title" i18n>Add to Selection List/Purchase Order</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <!--Only display if we're adding to a picklist-->
+ <ng-container *ngIf="addtopl">
+ <h3 i18n>Select an existing selection list:</h3>
+ <eg-combobox domId="eg-acq-create-select-dialog" name="eg-acq-create-select-dialog"
+ [asyncSupportsEmptyTermClick]="true"
+ idlClass="acqpl"
+ [(ngModel)]="pl"></eg-combobox>
+ <br>
+ <h3>Or create a new selection list:</h3>
+ <div>
+ <input [(ngModel)]="newPl" class="form-control" type="text"
+ name="plname" i18n-placeholder placeholder="Selection List Name..."/>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(newPl || pl.id)" i18n>Add to Selection List</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Exit Dialog</button>
+ </div>
+ </ng-container>
+ <!--Only display if we're adding to a purchase order-->
+ <ng-container *ngIf="addtopo">
+ <h3 i18n>Select an existing purchase order:</h3><eg-help-popover helpText="Only New and Pending purchase orders will appear in the list." i18n-helpText></eg-help-popover>
+ <eg-combobox domId="eg-acq-create-select-dialog" name="eg-acq-create-select-dialog"
+ [asyncSupportsEmptyTermClick]="true"
+ idlClass="acqpo" [idlQueryAnd]="{state: ['new', 'pending']}"
+ idlIncludeLibraryInLabel="ordering_agency"
+ [(ngModel)]="po"></eg-combobox>
+ <br>
+ <h3>Or create a new purchase order?</h3>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-info" [disabled]="po"
+ (click)="close('createNew')" i18n>Create New PO</button>
+ <button type="button" class="btn btn-success" [disabled]="!po"
+ (click)="close(po.id)" i18n>Add to Purchase Order</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Exit Dialog</button>
+ </div>
+ </ng-container>
+ </div>
+ </form>
+</ng-template>
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+<ng-template #dialogContent>
+ <form class="form-validated">
+ <div class="modal-header bg-info">
+ <h3 class="modal-title" i18n>Set Hold Preferences</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <h4 i18n>Should holds be placed for patrons for these requests?<br><eg-help-popover helpText="Only patron requests with New and Pending statuses are eligible to change hold preferences." i18n-help-Text></eg-help-popover>
+ <br></h4>
+ <b>These request IDs will be changed: </b> <span *ngFor="let eligibleReq of eligibleReqs; last as isLast">{{eligibleReq}}<span *ngIf="!isLast">,</span>
+ </span><br>
+ <b>These request IDs will <u>not</u> be changed: </b> <span *ngFor="let nonEligibleReq of nonEligibleReqs; last as isLast">{{nonEligibleReq}}<span *ngIf="!isLast">,</span>
+ </span>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(true)" i18n>Yes</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close(false)" i18n>No</button>
+ <button type="button" class="btn btn-danger"
+ (click)="close()" i18n>No Change</button>
+ </div>
+ </form>
+</ng-template>
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+<ng-template #dialogContent>
+ <form class="form-validated">
+ <div class="modal-header bg-info">
+ <h3 class="modal-title" i18n>Link to Line item</h3>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <h4>Marked Lineitem:</h4><br>
+ <b>ID:</b> {{lineitem}} <br>
+ <b>Title:</b> {{getTitle(lineitemFleshed)}} <br>
+ <b>Author:</b> {{getAuthor(lineitemFleshed)}}<br>
+ <b>Selection List:</b>
+ <ng-container *ngIf="lineitemFleshed.picklist()">
+ {{lineitemFleshed.picklist().name()}}
+ </ng-container> <br>
+ <b>Purchase Order:</b>
+ <ng-container *ngIf="lineitemFleshed.purchase_order()">
+ {{lineitemFleshed.purchase_order().name()}} (ID#{{lineitemFleshed.purchase_order().id()}})
+ </ng-container> <br>
+ <br>
+ <h4 i18n>Do you want to link the following patron requests to the above line item? :</h4> <span *ngFor="let eligibleReq of eligibleReqs; last as isLast">{{eligibleReq}}<span *ngIf="!isLast">,</span>
+ </span>
+ <br>
+ <ng-container *ngIf="nonEligibleReqs.length > 0">
+ <div><h4 i18n>The following requests will not be changed, as they are not in an eligible state:</h4> <span *ngFor="let nonEligibleReq of nonEligibleReqs; last as isLast">{{nonEligibleReq}}<span *ngIf="!isLast">,</span>
+ </span></div></ng-container>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(lineitem)" i18n>Confirm</button>
+ <button type="button" class="btn btn-warning"
+ (click)="close()" i18n>Cancel</button>
+ </div>
+ </form>
+</ng-template>
\ No newline at end of file
--- /dev/null
+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<any> {
+ 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
--- /dev/null
+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<any> {
+ return this.pcrud.autoApply(list);
+}
+
+createPicklist(name: string): Promise<Number> {
+
+ 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
--- /dev/null
+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 {}
path: 'related',
loadChildren: () =>
import('./related/related.module').then(m => m.RelatedModule)
+}, {
+ path: 'requests',
+ loadChildren: () =>
+ import('./requests/acq-requests.module').then(m => m.AcqRequestsModule)
}];
@NgModule({
(onClick)="deleteLineitems($event)" [disableOnRows]="noSelectedRows">
</eg-grid-toolbar-action>
<eg-grid-toolbar-action label="Export Single Attribute List" i18n-label
- (onClick)="exportSingleAttributeList($event)" [disableOnRows]="noSelectedRows">
+ (onClick)="exportSingleAttributeList($event)" [disableOnRows]="noSelectedRows"></eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Mark for Patron Requests" i18n-label
+ (onClick)="markLIforRequest($event)" [disableOnRows]="notOneSelectedRow">
</eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Clear Marked Line Item {{markedLi}}" i18n-label
+ (onClick)="clearMark($event)"></eg-grid-toolbar-action>
<eg-grid-column path="id" [cellTemplate]="idTmpl" [disableTooltip]="true"></eg-grid-column>
<eg-grid-column i18n-label label="Title" path="title" [cellTemplate]="liAttrTmpl"></eg-grid-column>
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';
@Input() initialSearchTerms: AcqSearchTerm[] = [];
+ markedLI: IdlObject;
+
gridSource: GridDataSource;
@ViewChild('acqSearchForm', { static: true}) acqSearchForm: AcqSearchFormComponent;
@ViewChild('acqSearchLineitemsGrid', { static: true }) lineitemResultsGrid: GridComponent;
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,
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 => {
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);
+ }
}
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,
--- /dev/null
+<eg-staff-banner bannerText="Cancel Reason Administration" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-title i18n-prefix prefix="Cancel Reason Administration"></eg-title>
+
+<ul ngbNav #cancelReasonsAdminNav="ngbNav" class="nav-tabs">
+ <li ngbNavItem>
+ <a ngbNavLink i18n>Order Cancel Reasons</a>
+ <ng-template ngbNavContent>
+ <div class="mt-2">
+ <eg-admin-page idlClass="acqcr"></eg-admin-page>
+ </div>
+ </ng-template>
+ </li>
+ <li ngbNavItem>
+ <a ngbNavLink i18n>Request Cancel Reasons</a>
+ <ng-template ngbNavContent>
+ <div class="mt-2">
+ <eg-admin-page idlClass="aurcr"></eg-admin-page>
+ </div>
+ </ng-template>
+ </li>
+</ul>
+<div [ngbNavOutlet]="cancelReasonsAdminNav"></div>
--- /dev/null
+import {Component} from '@angular/core';
+
+@Component({
+ templateUrl: './cancel-reasons-admin.component.html'
+})
+export class CancelReasonsComponent {
+}
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',
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
}, {
<span class="material-icons" aria-hidden="true">thumb_up</span>
<span i18n>Patron Requests</span>
</a>
+ <a *ngIf="requestsEnabled" class="dropdown-item"
+ routerLink="/staff/acq/requests">
+ <span class="material-icons" aria-hidden="true">thumb_up</span>
+ <span i18n>Patron Requests (NEW)</span>
+ </a>
<a class="dropdown-item"
href="/eg/staff/acq/legacy/picklist/bib_search">
<span class="material-icons" aria-hidden="true">cloud_download</span>
curbsideEnabled: boolean;
showAngularCirc = false;
maxRecentPatrons: number = 1;
+ requestsEnabled: boolean;
@ViewChild('navOpChange', {static: false}) opChange: OpChangeComponent;
permFailedSub: Subscription;
.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)
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
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(
}
$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;
$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;
$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);
}
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 {
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,
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);
'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'
+);
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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