LP1929741 Bundle of ACQ bug fixes
authorBill Erickson <berickxx@gmail.com>
Fri, 11 Jun 2021 15:29:47 +0000 (11:29 -0400)
committerJane Sandberg <js7389@princeton.edu>
Sun, 2 Oct 2022 15:02:49 +0000 (08:02 -0700)
* Distribution formula combobox now displays entries on click
* Ditto Charge Type combobox
* Items now vanish when deleted
* Estimated amount, etc. update when items are added/deleted
* Lineitem action links match order of previous interface.
* PO dry run checks now fire after a brief record is added
* PO activation is blocked if an item has no owning lib.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
15 files changed:
Open-ILS/src/eg2/src/app/staff/acq/lineitem/batch-copies.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/batch-copies.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/brief-record.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copies.component.html
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copies.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/copy-attrs.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-list.component.ts
Open-ILS/src/eg2/src/app/staff/acq/lineitem/lineitem.service.ts
Open-ILS/src/eg2/src/app/staff/acq/po/charges.component.html
Open-ILS/src/eg2/src/app/staff/acq/po/po.service.ts
Open-ILS/src/eg2/src/app/staff/acq/po/print.component.ts
Open-ILS/src/eg2/src/app/staff/acq/po/summary.component.html
Open-ILS/src/eg2/src/app/staff/acq/po/summary.component.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm

index 2fdfd2d..8a5bb4d 100644 (file)
@@ -45,7 +45,7 @@
 
 <div class="mt-1 pt-1 border-top">
   <div class="batch-copy-row" 
-    *ngFor="let copy of lineitem.lineitem_details(); let idx = index">
+    *ngFor="let copy of copies(); let idx = index">
     <eg-lineitem-copy-attrs 
       (receiveRequested)="receiveCopy($event)"
       (unReceiveRequested)="unReceiveCopy($event)"
index 8ce878c..6c70aa3 100644 (file)
@@ -148,6 +148,10 @@ export class LineitemBatchCopiesComponent implements OnInit {
         }
         return false;
     }
+
+    copies(): IdlObject[] {
+        return this.lineitem.lineitem_details().filter(c => !c.isdeleted());
+    }
 }
 
 
index f1384da..623d305 100644 (file)
@@ -4,6 +4,7 @@ import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {AuthService} from '@eg/core/auth.service';
+import {LineitemService} from './lineitem.service';
 
 const MARC_NS = 'http://www.loc.gov/MARC21/slim';
 
@@ -34,7 +35,8 @@ export class BriefRecordComponent implements OnInit {
         private idl: IdlService,
         private auth: AuthService,
         private net: NetService,
-        private pcrud: PcrudService
+        private pcrud: PcrudService,
+        private liService: LineitemService
     ) { }
 
     ngOnInit() {
@@ -103,6 +105,7 @@ export class BriefRecordComponent implements OnInit {
         this.net.request('open-ils.acq',
             'open-ils.acq.lineitem.create', this.auth.token(), li
         ).toPromise().then(_ => {
+            this.liService.activateStateChange.emit();
             this.router.navigate(['../'], {
                 relativeTo: this.route,
                 queryParamsHandling: 'merge'
index 0f768d8..3335cb4 100644 (file)
@@ -15,6 +15,7 @@
     <label class="ml-3" for='distrib-formula-cbox' i18n>Distribution Formulas</label>
     <span class="ml-3">
       <eg-combobox idlClass="acqdf" [idlQueryAnd]="formulaFilter" 
+        [asyncSupportsEmptyTermClick]="true"
         #distribFormCbox domId="distrib-formula-cbox">
       </eg-combobox>
     </span>
index e9f060e..ac5788e 100644 (file)
@@ -8,7 +8,7 @@ import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {AuthService} from '@eg/core/auth.service';
-import {LineitemService} from './lineitem.service';
+import {LineitemService, FleshCacheParams} from './lineitem.service';
 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {ItemLocationService} from '@eg/share/item-location-select/item-location-select.service';
 
@@ -74,11 +74,15 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
         this.liService.getLiAttrDefs();
     }
 
-    load(): Promise<any> {
+    load(params?: FleshCacheParams): Promise<any> {
         this.lineitem = null;
         this.copyCount = 1;
-        return this.liService.getFleshedLineitems(
-            [this.lineitemId], {toCache: true, fromCache: true})
+
+        if (!params) {
+            params = {toCache: true, fromCache: true};
+        }
+
+        return this.liService.getFleshedLineitems([this.lineitemId], params)
         .pipe(tap(liStruct => this.lineitem = liStruct.lineitem)).toPromise()
         .then(_ => {
             this.liLocked =
@@ -241,7 +245,7 @@ export class LineitemCopiesComponent implements OnInit, AfterViewInit {
                 this.progressValue++;
             },
             err => {},
-            () => this.load().then(_ => {
+            () => this.load({toCache: true}).then(_ => {
                 this.liService.activateStateChange.emit(this.lineitem.id());
                 this.saving = false;
             })
index 49d3a1a..f2d8952 100644 (file)
     <div class="flex-2 p-1 pr-2 pl-2">
       <ng-container *ngIf="!batchMode">
         <ng-container *ngIf="disposition() == 'pre-order'">
-          <button *ngIf="!copy.isdeleted()"
+          <button
             class="btn btn-outline-danger material-icon-button"
             (click)="deleteRequested.emit(copy)" title="Delete Item" i18n-title>
             <span class="material-icons">delete</span>
           </button>
-          <button  *ngIf="copy.isdeleted()"
-            class="btn btn-outline-info material-icon-button"
-            (click)="copy.isdeleted(false)" title="Un-Delete Item" i18n-title>
-            <span class="material-icons">restore_page</span>
-          </button>
         </ng-container>
         <ng-container *ngIf="disposition() == 'on-order'">
           <a href="javascript:;" (click)="receiveRequested.emit(copy)" i18n>Mark Received</a>
index bab495d..b206f7e 100644 (file)
               <span *ngIf="liHasAlerts(li)" class="text-danger material-icons"
                 title="Has Alerts" i18n-title>flag</span>
             </a>
-            <span class="ml-1 mr-1" i18n> | </span>
-            <a class="label-with-material-icon"
-              routerLink="lineitem/{{li.id()}}/worksheet/">
-              <span class="material-icons small mr-1">create</span>
-              <span i18n>Worksheet</span>
-            </a>
-            <span class="ml-1 mr-1" i18n> | </span>
-            <a class="label-with-material-icon"
-              [queryParams]="{f: 'jub:id', val1: li.id()}"
-              routerLink="/staff/acq/search/invoices">
-              <span class="material-icons small mr-1">list</span>
-              <span i18n>Invoice(s)</span>
-            </a>
             <ng-container *ngIf="li.eg_bib_id()">
               <span class="ml-1 mr-1" i18n> | </span>
               <a class="label-with-material-icon mr-2"
               </a>
             </ng-container>
 
-            <ng-container *ngIf="!poId && li.purchase_order()">
-              <span class="ml-1 mr-1" i18n> | </span>
-              <a class="label-with-material-icon"
-                title="Purchase Order" i18n-title
-                routerLink="/staff/acq/po/{{li.purchase_order().id()}}">
-                <span class="material-icons small mr-1">center_focus_weak</span>
-                <span i18n>{{li.purchase_order().id()}}</span>
-              </a>
-            </ng-container>
+            <!-- TODO link to catalog -->
+
+            <span class="ml-1 mr-1" i18n> | </span>
+            <a class="label-with-material-icon"
+              routerLink="lineitem/{{li.id()}}/worksheet/">
+              <span class="material-icons small mr-1">create</span>
+              <span i18n>Worksheet</span>
+            </a>
             <ng-container *ngIf="!picklistId && li.picklist() && li.picklist().name()">
               <span class="ml-1 mr-1" i18n> | </span>
               <a class="label-with-material-icon"
                 <span i18n>{{li.picklist().name()}}</span>
               </a>
             </ng-container>
+            <ng-container *ngIf="!poId && li.purchase_order()">
+              <span class="ml-1 mr-1" i18n> | </span>
+              <a class="label-with-material-icon"
+                title="Purchase Order" i18n-title
+                routerLink="/staff/acq/po/{{li.purchase_order().id()}}">
+                <span class="material-icons small mr-1">center_focus_weak</span>
+                <span i18n>{{li.purchase_order().id()}}</span>
+              </a>
+            </ng-container>
+
+            <!-- TODO patron requests -->
+
+            <span class="ml-1 mr-1" i18n> | </span>
+            <a class="label-with-material-icon"
+              [queryParams]="{f: 'jub:id', val1: li.id()}"
+              routerLink="/staff/acq/search/invoices">
+              <span class="material-icons small mr-1">list</span>
+              <span i18n>Invoice(s)</span>
+            </a>
+
+            <!-- TODO: claim policy -->
+
             <ng-container *ngIf="li.provider()">
               <span class="ml-1 mr-1" i18n> | </span>
               <a class="label-with-material-icon"
                 <span i18n>{{li.provider().name()}}</span>
               </a>
             </ng-container>
+
+            <!-- TODO import queue -->
+
           </div>
         </div>
       </div>
index e29938f..ce90fe9 100644 (file)
@@ -15,7 +15,7 @@ import {CancelDialogComponent} from './cancel-dialog.component';
 
 const DELETABLE_STATES = [
     'new', 'selector-ready', 'order-ready', 'approved', 'pending-order'
-]
+];
 
 @Component({
   templateUrl: 'lineitem-list.component.html',
@@ -263,6 +263,10 @@ export class LineitemListComponent implements OnInit {
         } else {
             this.pageOfLineitems.push(li);
         }
+
+        // Remove any 'new' lineitem details which may have been added
+        // and left unsaved on the copies page
+        li.lineitem_details(li.lineitem_details().filter(d => !d.isnew()));
     }
 
     // First matching attr
index 379f94d..f669555 100644 (file)
@@ -31,7 +31,7 @@ export interface BatchLineitemUpdateStruct {
     [key: string]: any; // Perl Acq::BatchManager
 }
 
-interface FleshedLineitemParams {
+export interface FleshCacheParams {
 
     // Flesh data beyond the default.
     fleshMore?: any;
@@ -79,7 +79,7 @@ export class LineitemService {
     ) {}
 
     getFleshedLineitems(ids: number[],
-        params: FleshedLineitemParams = {}): Observable<BatchLineitemStruct> {
+        params: FleshCacheParams = {}): Observable<BatchLineitemStruct> {
 
         if (params.fromCache) {
             const fromCache = this.getLineitemsFromCache(ids);
index 753f730..a51e067 100644 (file)
@@ -17,6 +17,7 @@
     *ngFor="let charge of po().po_items()">
     <div class="flex-2 p-2">
       <eg-combobox idlClass="aiit" [selectedId]="charge.inv_item_type()"
+        [asyncSupportsEmptyTermClick]="true"
         (onChange)="charge.inv_item_type($event ? $event.id : null)"
         i18n-placeholder placeholder="Charge Type..."
         [required]="true" [readOnly]="!charge.isnew()"></eg-combobox>
index 4678189..25d1924 100644 (file)
@@ -6,6 +6,7 @@ import {EventService} from '@eg/core/event.service';
 import {NetService} from '@eg/core/net.service';
 import {AuthService} from '@eg/core/auth.service';
 import {PcrudService} from '@eg/core/pcrud.service';
+import {LineitemService, FleshCacheParams} from '@eg/staff/acq/lineitem/lineitem.service';
 
 @Injectable()
 export class PoService {
@@ -20,9 +21,9 @@ export class PoService {
         private auth: AuthService
     ) {}
 
-    getFleshedPo(id: number, fleshMore?: any, noCache?: boolean): Promise<IdlObject> {
+    getFleshedPo(id: number, params: FleshCacheParams = {}): Promise<IdlObject> {
 
-        if (!noCache) {
+        if (params.fromCache) {
             if (this.currentPo && id === this.currentPo.id()) {
                 // Set poService.currentPo = null to bypass the cache
                 return Promise.resolve(this.currentPo);
@@ -35,7 +36,7 @@ export class PoService {
             flesh_po_items: true,
             flesh_price_summary: true,
             flesh_lineitem_count: true
-        }, fleshMore || {});
+        }, params.fleshMore || {});
 
         return this.net.request(
             'open-ils.acq',
@@ -46,7 +47,7 @@ export class PoService {
             const evt = this.evt.parse(po);
             if (evt) { return Promise.reject(evt + ''); }
 
-            if (!noCache) { this.currentPo = po; }
+            if (params.toCache) { this.currentPo = po; }
 
             this.poRetrieved.emit(po);
             return po;
index 2ed659a..beadb5d 100644 (file)
@@ -57,14 +57,16 @@ export class PrintComponent implements OnInit, AfterViewInit {
 
         this.po = null;
         this.poService.getFleshedPo(this.poId, {
-            flesh_provider_addresses: true,
-            flesh_lineitems: true,
-            flesh_lineitem_attrs: true,
-            flesh_lineitem_notes: true,
-            flesh_lineitem_details: true,
-            clear_marc: true,
-            flesh_notes: true
-        }, true)
+            fleshMore: {
+                flesh_provider_addresses: true,
+                flesh_lineitems: true,
+                flesh_lineitem_attrs: true,
+                flesh_lineitem_notes: true,
+                flesh_lineitem_details: true,
+                clear_marc: true,
+                flesh_notes: true
+            }
+        })
         .then(po => this.po = po)
         .then(_ => this.populatePreview())
         .then(_ => this.initDone = true);
index 8c05c15..95bd00e 100644 (file)
               </ng-template>
               <ng-template #noCopies>
                 <ng-container 
-                  *ngIf="evt.textcode == 'ACQ_LINEITEM_NO_COPIES'; else otherBlock">
+                  *ngIf="evt.textcode == 'ACQ_LINEITEM_NO_COPIES'; else noOwner">
                   <span i18n>One or more lineitems have no items attached.</span>
                 </ng-container>
               </ng-template>
+              <ng-template #noOwner>
+                <ng-container 
+                  *ngIf="evt.textcode == 'ACQ_COPY_NO_OWNING_LIB'; else otherBlock">
+                  <span i18n>One or more items have no owning lib.</span>
+                </ng-container>
+              </ng-template>
               <ng-template #otherBlock>
                 <span i18n>{{evt.textcode}} : {{evt.desc}}</span>
               </ng-template>
index 21799f2..2cef47f 100644 (file)
@@ -66,6 +66,7 @@ export class PoSummaryComponent implements OnInit {
         // Re-check for activation blocks if the LI service tells us
         // something significant happened.
         this.liService.activateStateChange
+        .pipe(tap(_ => this.poService.getFleshedPo(this.poId, {toCache: true})))
         .subscribe(_ => this.setCanActivate());
     }
 
@@ -76,7 +77,7 @@ export class PoSummaryComponent implements OnInit {
     load(): Promise<any> {
         if (!this.poId) { return Promise.resolve(); }
 
-        return this.poService.getFleshedPo(this.poId)
+        return this.poService.getFleshedPo(this.poId, {fromCache: true, toCache: true})
         .then(po => {
 
             // EDI message count
index ad2d46d..475c61f 100644 (file)
@@ -911,6 +911,14 @@ sub create_lineitem_debits {
             }
         ]);
 
+        if (!$lid->owning_lib) {
+            # It's OK to create copies with no owning lib, but activating
+            # an order with such copies creates problems.
+            $mgr->editor->event(OpenILS::Event->new('ACQ_COPY_NO_OWNING_LIB', payload => $li->id));
+            $mgr->editor->rollback;
+            return 0;
+        }
+
         create_lineitem_detail_debit($mgr, $li, $lid, $dry_run) or return 0;
     }