LP1952931 ASN Receiving UI
authorBill Erickson <berickxx@gmail.com>
Wed, 8 Dec 2021 22:37:39 +0000 (17:37 -0500)
committerBill Erickson <berickxx@gmail.com>
Wed, 21 Sep 2022 15:29:35 +0000 (11:29 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/acq/asn/receive.component.html
Open-ILS/src/eg2/src/app/staff/acq/asn/receive.component.ts
Open-ILS/src/eg2/src/app/staff/acq/asn/routing.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html

index 828dc61..68d4005 100644 (file)
   </div>
 </div>
 
+<!--
 <hr class="mt-2 mb-2"/>
+-->
 
-<div class="row border rounded mt-1" *ngFor="let container of containers">
-  <div class="col-lg-12 d-flex pt-2 pb-2">
-    <div class="">{{container.container_code()}}</div>
-    <div class="ml-2">{{container.provider().name()}}</div>
-    <div class="ml-2">{{container.recv_date() | date:'short'}}</div>
-    <div class="ml-2">{{container.lading_number()}}</div>
-    <div class="ml-2">{{container.note()}}</div>
+<!-- TODO Unlikely, but technically possible for multiple containers
+across different vendors to match a container code. 
+<div *ngFor="let container of containers">
+...
+</div>
+-->
+
+<div *ngIf="container" class="mt-3 mb-3 p-1 shadow-sm common-form striped-odd">
+  <div class="row">
+    <div class="col-lg-2">
+      <label for="container-code" i18n>Container Code: </label>
+    </div>
+    <div class="col-lg-2">
+      <div id="container-code">{{container.container_code()}}</div>
+    </div>
+    <div class="col-lg-2">
+      <label for="container-provider" i18n>Provider: </label>
+    </div>
+    <div class="col-lg-2">
+      <div>
+        <a target="_blank"
+          id="container-provider"
+          routerLink="/staff/acq/provider/{{container.provider().id()}}/details">
+          {{container.provider().name()}} ({{container.provider().code()}})
+        </a>
+      </div>
+    </div>
+    <div class="col-lg-2">
+      <label for="entry-count" i18n>Affected Lineitems: </label>
+    </div>
+    <div class="col-lg-2">
+      <div id="entry-count">{{entries.length}}</div>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-lg-2">
+      <label for="container-lading-number" i18n>Lading #: </label>
+    </div>
+    <div class="col-lg-2">
+      <div id="container-lading-number">{{container.lading_number()}}</div>
+    </div>
+    <div class="col-lg-2">
+      <label for="container-recv-date" i18n>Receive Date: </label>
+    </div>
+    <div class="col-lg-2">
+      <div id="container-recv-date">{{container.recv_date() | date:'short'}}</div>
+    </div>
+    <div class="col-lg-2">
+      <label for="entry-count" i18n>Affected Items: </label>
+    </div>
+    <div class="col-lg-2">
+      <div id="entry-count">{{affectedItemsCount()}}</div>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-lg-2">
+      <label for="container-note" i18n>Notes: </label>
+    </div>
+    <div class="col-lg-4">
+      <div class="ml-1">{{container.note()}}</div>
+    </div>
   </div>
 </div>
+
+<!--
+<hr class="mt-2 mb-2"/>
+-->
+
+<ng-template #titleTmpl let-row="row">
+  <a target="_blank" 
+    fragment="{{row.lineitem.id()}}"
+    routerLink="/staff/acq/po/{{row.lineitem.purchase_order().id()}}">
+    {{row.title}}
+  </a>
+</ng-template>
+<ng-template #liIdTmpl let-row="row">
+  <a target="_blank" 
+    fragment="{{row.lineitem.id()}}"
+    routerLink="/staff/acq/po/{{row.lineitem.purchase_order().id()}}">
+    {{row.lineitem.id()}}
+  </a>
+</ng-template>
+<ng-template #poNameTmpl let-row="row">
+  <a target="_blank" 
+    routerLink="/staff/acq/po/{{row.lineitem.purchase_order().id()}}">
+    {{row.lineitem.purchase_order().name()}}
+  </a>
+</ng-template>
+
+<eg-grid *ngIf="container" #grid [dataSource]="gridDataSource" 
+  pageSize="50" (onRowActivate)="openLi($event)">
+
+  <eg-grid-toolbar-button i18n-label label="Receive All Items"
+    (onClick)="receiveAllItems()"></eg-grid-toolbar-button> 
+
+  <eg-grid-column i18n-label label="Entry ID" path="entry.id" 
+    [index]="true" [hidden]="true"></eg-grid-column>
+  <eg-grid-column i18n-label label="Lineitem ID" name="lineitem_id" 
+    [cellTemplate]="liIdTmpl"></eg-grid-column>
+  <eg-grid-column i18n-label label="Purchase Order" name="po_name" 
+    [cellTemplate]="poNameTmpl"></eg-grid-column>
+  <eg-grid-column i18n-label label="Title" name="title" flex="4" 
+    [cellTemplate]="titleTmpl"></eg-grid-column>
+  <eg-grid-column i18n-label label="ISBN" path="isbn"></eg-grid-column>
+  <eg-grid-column i18n-label label="ISSN" path="issn" [hidden]="true"></eg-grid-column>
+  <eg-grid-column i18n-label label="UPC" path="upc" [hidden]="true"></eg-grid-column>
+  <eg-grid-column i18n-label label="In Shipment" path="entry.item_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Ordered" path="lineitem.order_summary.item_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Pending Receive" path="recievable_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Received" path="lineitem.order_summary.recv_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Invoiced" path="lineitem.order_summary.invoice_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Canceled" path="lineitem.order_summary.cancel_count"></eg-grid-column>
+  <eg-grid-column i18n-label label="Delayed" path="lineitem.order_summary.delay_count"></eg-grid-column>
+</eg-grid>
+
+
index cb53499..0f5ad15 100644 (file)
@@ -1,8 +1,14 @@
-import {Component, OnInit} from '@angular/core';
+import {Component, OnInit, ViewChild} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {Location} from '@angular/common';
+import {Observable, Observer, of, from} from 'rxjs';
+import {tap} from 'rxjs/operators';
 import {IdlObject} from '@eg/core/idl.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {LineitemService} from '../lineitem/lineitem.service';
+import {Pager} from '@eg/share/util/pager';
+import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
 
 @Component({
   templateUrl: 'receive.component.html'
@@ -11,28 +17,123 @@ export class AsnReceiveComponent implements OnInit {
 
     barcode = '';
 
-    // Technically possible for one barcode to match across providers.
+    // Technically possible for one container code to match across providers.
+    container: IdlObject;
+    entries: IdlObject[] = [];
     containers: IdlObject[] = [];
 
+    @ViewChild('grid') private grid: GridComponent;
+    gridDataSource: GridDataSource = new GridDataSource();
+
     constructor(
         private route: ActivatedRoute,
+        private router: Router,
+        private ngLocation: Location,
         private pcrud: PcrudService,
         private li: LineitemService
     ) {}
 
     ngOnInit() {
-    }
+        this.barcode = this.route.snapshot.paramMap.get('containerCode') || '';
+        if (this.barcode) {
+            this.findContainer();
+        }
 
-    findContainer() {
+        this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+            return from(this.entries.map(e => this.gridifyEntry(e)));
+        };
+    }
 
-        console.log('BARCODE', this.barcode);
+    gridifyEntry(entry: IdlObject): any {
+        const li = entry.lineitem();
+        const sum = li.order_summary();
+        return {
+            entry: entry,
+            lineitem: li,
+            title: this.li.getFirstAttributeValue(li, 'title'),
+            author: this.li.getFirstAttributeValue(li, 'author'),
+            isbn: this.li.getFirstAttributeValue(li, 'isbn'),
+            issn: this.li.getFirstAttributeValue(li, 'issn'),
+            upc: this.li.getFirstAttributeValue(li, 'upc'),
+            recievable_count: sum.item_count() - (
+                sum.recv_count() + sum.cancel_count()
+            )
+        };
+    }
 
+    findContainer() {
+        this.container = null;
         this.containers = [];
+        this.entries = [];
 
         this.pcrud.search('acqsn',
             {container_code: this.barcode},
             {flesh: 1, flesh_fields: {acqsn: ['entries', 'provider']}}
-        ).subscribe(sn => this.containers.push(sn));
+        ).subscribe(
+          sn => this.containers.push(sn),
+          _ => {},
+          () => {
+
+              // TODO handle multiple containers w/ same code
+              if (this.containers.length === 1) {
+                  this.container = this.containers[0];
+                  this.loadContainer();
+              }
+
+              const node = document.getElementById('barcode-search-input');
+              (node as HTMLInputElement).select();
+          }
+        );
+    }
+
+    loadContainer() {
+        if (!this.container) { return; }
+
+        const entries = this.container.entries();
+
+        if (entries.length === 0) { return; }
+
+        this.li.getFleshedLineitems(entries.map(e => e.lineitem()), {})
+        .subscribe(
+            li_struct => {
+                // Flesh the lineitems directly in the shipment entry
+                const entry = entries.filter(e => e.lineitem() === li_struct.id)[0];
+                entry.lineitem(li_struct.lineitem);
+            },
+            _ => {},
+            () => {
+                this.entries = entries;
+                this.grid.reload();
+            }
+        );
+    }
+
+    openLi(row: any) {
+        let url = this.ngLocation.prepareExternalUrl(
+            this.router.serializeUrl(
+                this.router.createUrlTree(
+                    ['/staff/acq/po/', row.lineitem.purchase_order().id()]
+                )
+            )
+        );
+
+        // this.router.createUrlTree() documents claim it supports
+        // {fragment: row.lineitem.id()}, but it's not getting added to
+        // the URL. Adding manually.
+        url += '#' + row.lineitem.id();
+
+        window.open(url);
+    }
+
+    affectedItemsCount(): number {
+        if (this.entries.length === 0) { return 0; }
+        return this.entries
+            .map(e => e.item_count())
+            .reduce((pv, cv) => pv + (cv || 0));
+    }
+
+    receiveAllItems() {
+        alert('TODO');
     }
 }
 
index ca60120..b4d0f67 100644 (file)
@@ -12,6 +12,10 @@ import {AsnReceiveComponent} from './receive.component';
 const routes: Routes = [{
   path: 'receive',
   component: AsnReceiveComponent
+}, {
+  path: 'receive/:containerCode',
+  component: AsnReceiveComponent
+
 }];
 
 @NgModule({
index 839db08..4d1767f 100644 (file)
             <span i18n>Claim-Ready Items</span>
           </a>
           <a class="dropdown-item"
+            routerLink="/staff/acq/asn/receive">
+            <span class="material-icons" aria-hidden="true">archive</span>
+            <span i18n>Receive Shipment</span>
+          </a>
+          <a class="dropdown-item"
             routerLink="/staff/acq/search/invoices">
             <span class="material-icons" aria-hidden="true">attach_money</span>
             <span i18n>Invoices</span>