LP1988993 Patron requests user/tlittle/LP1988993_patronrequests
authorTiffany Little <tlittle@georgialibraries.org>
Wed, 10 May 2023 14:05:46 +0000 (10:05 -0400)
committerTiffany Little <tlittle@georgialibraries.org>
Wed, 10 May 2023 14:05:46 +0000 (10:05 -0400)
Angularizes the acquisitions patron requests interface.

Adds a new table for request-specific cancel reasons.

Signed-off-by: Tiffany Little <tlittle@georgialibraries.org>
32 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/cancel-dialog.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem-list.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.module.ts
Open-ILS/src/eg2/src/app/staff/acq/po/create.component.ts
Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/create-select-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/hold-pref-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/link-to-lineitem-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/requests.service.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/requests/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/routing.module.ts
Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.html
Open-ILS/src/eg2/src/app/staff/acq/search/lineitem-results.component.ts
Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts
Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/cancel-reasons-admin.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/nav.component.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
Open-ILS/src/sql/Pg/200.schema.acq.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.enable_acq_patron_requests.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq-usr-req-status.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.acq.request_cancelreasons.sql [new file with mode: 0644]

index 949ed2b..d9bf7a3 100644 (file)
@@ -9242,6 +9242,7 @@ SELECT  usr,
                        <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"/>
@@ -9249,7 +9250,7 @@ SELECT  usr,
                        <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>
@@ -9269,6 +9270,26 @@ SELECT  usr,
         </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
@@ -9338,19 +9359,6 @@ SELECT  usr,
         </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'/>
index 3d5912e..5b14200 100644 (file)
@@ -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.
index b56ee63..b51d224 100644 (file)
@@ -6,6 +6,11 @@
       <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">&times;</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" 
index 5845599..954ec9b 100644 (file)
             <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>
index 89d2780..e11bc49 100644 (file)
@@ -58,6 +58,7 @@ import {AcqCommonModule} from '../acq-common.module';
     LineitemWorksheetComponent
   ],
   exports: [
+    BriefRecordComponent,
     LineitemListComponent,
     CancelDialogComponent,
     AddToPoDialogComponent,
index 7655529..8db98e6 100644 (file)
@@ -28,7 +28,7 @@ const VALID_PRE_PO_LI_STATES = [
   selector: 'eg-acq-po-create'
 })
 export class PoCreateComponent implements OnInit {
-
+    @Input() pofromreq: boolean;
     initDone = false;
     lineitems: number[] = [];
     origLiCount = 0;
@@ -103,7 +103,7 @@ export class PoCreateComponent implements OnInit {
     }
 
     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);
@@ -119,10 +119,16 @@ export class PoCreateComponent implements OnInit {
             '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()]);
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.html b/Open-ILS/src/eg2/src/app/staff/acq/requests/acq-requests.component.html
new file mode 100644 (file)
index 0000000..c9a9e7d
--- /dev/null
@@ -0,0 +1,163 @@
+<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>
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 (file)
index 0000000..53c5f87
--- /dev/null
@@ -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<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());
+      }
+    )
+  }
+    
+  }
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 (file)
index 0000000..ec8d79e
--- /dev/null
@@ -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 (file)
index 0000000..170e7b8
--- /dev/null
@@ -0,0 +1,56 @@
+<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">&times;</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
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 (file)
index 0000000..32a40e4
--- /dev/null
@@ -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 (file)
index 0000000..5b870dc
--- /dev/null
@@ -0,0 +1,27 @@
+<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">&times;</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
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 (file)
index 0000000..b66b643
--- /dev/null
@@ -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 (file)
index 0000000..4aec5cf
--- /dev/null
@@ -0,0 +1,38 @@
+<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">&times;</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
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 (file)
index 0000000..c960bb1
--- /dev/null
@@ -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<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
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 (file)
index 0000000..b74fa1d
--- /dev/null
@@ -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<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
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 (file)
index 0000000..1a091aa
--- /dev/null
@@ -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 {}
index 926a680..aac1308 100644 (file)
@@ -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({
index b0c8dd1..79ac52d 100644 (file)
     (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>
index 6a30b78..321f554 100644 (file)
@@ -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);
+    }
 }
index 067ea39..5dd2e04 100644 (file)
@@ -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 (file)
index 0000000..ff7b0c4
--- /dev/null
@@ -0,0 +1,24 @@
+<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>
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 (file)
index 0000000..739c7b6
--- /dev/null
@@ -0,0 +1,7 @@
+import {Component} from '@angular/core';
+
+@Component({
+    templateUrl: './cancel-reasons-admin.component.html'
+})
+export class CancelReasonsComponent {
+}
index 254f650..fadbab7 100644 (file)
@@ -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
 }, {
index d7db8bd..52ee872 100644 (file)
             <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>
index d4201a8..1a31423 100644 (file)
@@ -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
index 81c2b6c..9147b4f 100644 (file)
@@ -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 {
index 3195fee..dc0786d 100644 (file)
@@ -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);
index ec54030..dafb87b 100644 (file)
@@ -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 (file)
index 0000000..9edeebd
--- /dev/null
@@ -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 (file)
index 0000000..d0885fa
--- /dev/null
@@ -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 (file)
index 0000000..ccb6f16
--- /dev/null
@@ -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