LP1929741 View/Place Orders UI
authorBill Erickson <berickxx@gmail.com>
Tue, 26 Jan 2021 18:26:35 +0000 (13:26 -0500)
committerJane Sandberg <js7389@princeton.edu>
Sun, 2 Oct 2022 15:02:49 +0000 (08:02 -0700)
View/Place orders from a bib record and add/create selection lists or
PO's from the record.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
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/related/related.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/related/related.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/related/related.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/acq/related/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/catalog/record/actions.component.html
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Lineitem.pm

index ce7c5e8..db9d35b 100644 (file)
@@ -2,7 +2,7 @@
 <!-- BATCH ACTIONS -->
 <eg-acq-cancel-dialog #cancelDialog></eg-acq-cancel-dialog>
 
-<div class="row mt-3">
+<div class="row mt-3" *ngIf="poId || picklistId">
   <div class="col-lg-1">
     <div ngbDropdown>
       <button class="btn btn-info btn-sm" ngbDropdownToggle i18n>Actions</button>
@@ -62,7 +62,8 @@
 
 <!-- NAVIGATION / EXPANDY -->
 
-<div class="row mt-3 mb-1 border border-info rounded toolbar">
+<div *ngIf="poId || picklistId"
+  class="row mt-3 mb-1 border border-info rounded toolbar">
   <div class="col-lg-12 d-flex">
     <div class="d-flex justify-content-center flex-column h-100">
       <div class="form-check">
 
 <!-- LINEITEM LIST -->
 
+<ng-container *ngIf="pageOfLineitems.length === 0 && !loading">
+  <div class="row mt-2">
+    <div class="col-lg-6 offset-lg-3 alert alert-warning" i18n>
+      No items to display.
+    </div>
+  </div>
+</ng-container>
+
 <ng-container *ngFor="let li of pageOfLineitems">
   <div class="row mt-2 border-bottom pt-2 pb-2 li-state-{{li.state()}}">
     <div class="col-lg-12 d-flex">
index 46765a2..883d0b9 100644 (file)
@@ -22,6 +22,7 @@ export class LineitemListComponent implements OnInit {
 
     picklistId: number = null;
     poId: number = null;
+    recordId: number = null; // lineitems related to a bib.
 
     loading = false;
     pager: Pager = new Pager();
@@ -81,6 +82,7 @@ export class LineitemListComponent implements OnInit {
         this.route.parent.paramMap.subscribe((params: ParamMap) => {
             this.picklistId = +params.get('picklistId');
             this.poId = +params.get('poId');
+            this.recordId = +params.get('recordId');
             this.load();
         });
 
@@ -111,8 +113,8 @@ export class LineitemListComponent implements OnInit {
     load(): Promise<any> {
         this.pageOfLineitems = [];
 
-        if (!this.loading &&
-            this.pager.limit && (this.poId || this.picklistId)) {
+        if (!this.loading && this.pager.limit &&
+            (this.poId || this.picklistId || this.recordId)) {
 
             this.loading = true;
 
@@ -136,10 +138,18 @@ export class LineitemListComponent implements OnInit {
         let handler = (po) => po.lineitems();
 
         if (this.picklistId) {
+
             id = this.picklistId;
             options = {idlist: true, limit: 1000};
             method = 'open-ils.acq.lineitem.picklist.retrieve.atomic';
             handler = (ids) => ids;
+
+        } else if (this.recordId) {
+
+            id = this.recordId;
+            method = 'open-ils.acq.lineitems_for_bib.by_bib_id.atomic';
+            options = {idlist: true, limit: 1000};
+            handler = (ids) => ids;
         }
 
         return this.net.request(
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/related/related.component.html b/Open-ILS/src/eg2/src/app/staff/acq/related/related.component.html
new file mode 100644 (file)
index 0000000..6d7ed1f
--- /dev/null
@@ -0,0 +1,68 @@
+
+<eg-staff-banner i18n-bannerText bannerText="Linetiems Related to Bib Record">
+</eg-staff-banner>
+
+<eg-bib-summary [recordId]="recordId"></eg-bib-summary> 
+
+<eg-prompt-dialog #newPlDialog
+  i18n-dialogTitle dialogTitle="New Selection List"
+  i18n-dialogBody dialogBody="Enter Selection List Name">
+</eg-prompt-dialog>
+
+<eg-alert-dialog #plNameExists
+  i18n-dialogBody dialogBody="Selection List Name Already In Use">
+</eg-alert-dialog>
+
+<div class="row mt-2 mb-2 border border-info">
+  <div class="col-lg-2 p-1">
+    <button class="btn btn-outline-dark label-with-material-icon"
+      (click)="createPicklist()" i18n>
+      <span class="material-icons mr-1">create_new_folder</span>
+      Create Selection List
+    </button>
+  </div>
+  <div class="col-lg-2 p-1">
+    <button class="btn btn-outline-dark label-with-material-icon" 
+      (click)="addingToPl=!addingToPl" i18n>
+      <span class="material-icons mr-1">add_circle</span>
+      Add To Selection List
+    </button>
+  </div>
+  <div class="col-lg-2 p-1">
+    <div *ngIf="addingToPl" class="d-flex">
+      <eg-combobox [(ngModel)]="selectedPl" idlClass="acqpl"
+        placeholder="Select List..." i18n-placeHolder></eg-combobox>
+      <button class="btn btn-outline-primary btn-sm" 
+        (click)="addToPicklist(selectedPl ? selectedPl.id : null)" i18n>
+        Apply
+      </button>
+    </div>
+  </div>
+  <div class="col-lg-2 p-1">
+    <button class="btn btn-outline-dark label-with-material-icon"
+      (click)="createPo()" i18n>
+      <span class="material-icons mr-1">create_new_folder</span>
+      Create Purchase Order
+    </button>
+  </div>
+  <div class="col-lg-2 p-1">
+    <button class="btn btn-outline-dark label-with-material-icon" 
+      (click)="addingToPo=!addingToPo" i18n>
+      <span class="material-icons mr-1">add_circle_outline</span>
+      Add To Purchase Order
+    </button>
+  </div>
+  <div class="col-lg-2 p-1">
+    <div *ngIf="addingToPo" class="d-flex">
+      <eg-combobox [(ngModel)]="selectedPo" idlClass="acqpo"
+        placeholder="Select Order..." i18n-placeHolder></eg-combobox>
+      <button class="btn btn-outline-primary btn-sm" 
+        (click)="addToPo(selectedPo ? selectedPo.id : null)" i18n>
+        Apply
+      </button>
+    </div>
+  </div>
+</div>
+
+<router-outlet></router-outlet>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/related/related.component.ts b/Open-ILS/src/eg2/src/app/staff/acq/related/related.component.ts
new file mode 100644 (file)
index 0000000..6e91d52
--- /dev/null
@@ -0,0 +1,166 @@
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {tap} from 'rxjs/operators';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+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 {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+  templateUrl: 'related.component.html'
+})
+export class RelatedComponent implements OnInit {
+
+    recordId: number;
+    addingToPl = false;
+    addingToPo = false;
+    selectedPl: ComboboxEntry;
+    selectedPo: ComboboxEntry;
+
+    @ViewChild('newPlDialog') newPlDialog: PromptDialogComponent;
+    @ViewChild('plNameExists') plNameExists: AlertDialogComponent;
+
+    constructor(
+        private router: Router,
+        private route: ActivatedRoute,
+        private idl: IdlService,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private pcrud: PcrudService
+    ) {}
+
+    ngOnInit() {
+        this.route.paramMap.subscribe((params: ParamMap) => {
+            this.recordId = +params.get('recordId');
+        });
+    }
+
+    isBasePage(): boolean {
+        return !this.route.firstChild ||
+            this.route.firstChild.snapshot.url.length === 0;
+    }
+
+    // Create a new selection list
+    // Create and add the lineitem.
+    // Navigate to the new SL
+    createPicklist() {
+
+        this.newPlDialog.open().toPromise()
+        .then(name => {
+            if (!name) { return; }
+
+            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) { return; }
+
+            if (info.existing) {
+                // Alert the user the requested name is already in
+                // use and reopen the create dialog.
+                this.plNameExists.open().toPromise().then(_ => this.createPicklist());
+                return;
+            }
+
+            const pl = this.idl.create('acqpl');
+            pl.name(info.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; }
+
+            const evt = this.evt.parse(plId);
+            if (evt) { alert(evt); return; }
+
+            this.addToPicklist(plId);
+        });
+    }
+
+    createLineitem(options?: any): Promise<IdlObject> {
+
+        return this.net.request(
+            'open-ils.acq',
+            'open-ils.acq.biblio.create_by_id',
+            this.auth.token(), [this.recordId], options
+
+        ).pipe(tap(resp => {
+
+            if (Number(resp) > 0) {
+                // The API first responds with the picklist ID.
+                // Avoid navigating away from this page until the API
+                // completes and we know the lineitem has been created.
+                return;
+            }
+
+            const evt = this.evt.parse(resp);
+            if (evt) {
+                alert(evt);
+
+            } else {
+                return resp;
+            }
+
+        })).toPromise();
+    }
+
+    // Add a lineitem based on our bib record to the selected
+    // picklist by ID then navigate to that picklist's page.
+    addToPicklist(plId: number) {
+        if (!plId) { return; }
+
+        this.createLineitem({reuse_picklist: plId})
+        .then(li => {
+            if (li) {
+                this.router.navigate(['/staff/acq/picklist', plId]);
+            }
+        });
+    }
+
+    // Create the lineitem, then send the user to the new PO UI.
+    createPo() {
+        this.createLineitem().then(li => {
+            if (li) {
+                this.router.navigate(
+                    ['/staff/acq/po/create'], {queryParams: {li: li.id()}});
+            }
+        });
+    }
+
+    addToPo(poId: number) {
+        if (!poId) { return; }
+
+        this.createLineitem().then(li => {
+            if (!li) { return null; }
+
+            return this.net.request(
+                'open-ils.acq',
+                'open-ils.acq.purchase_order.add_lineitem',
+                this.auth.token(), poId, li.id()
+
+            ).toPromise();
+
+        }).then(resp => {
+            if (!resp) { return; }
+            const evt = this.evt.parse(resp);
+            if (evt) {
+                alert(evt);
+            } else {
+                this.router.navigate(['/staff/acq/po/', poId]);
+            }
+        });
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/related/related.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/related/related.module.ts
new file mode 100644 (file)
index 0000000..a02996a
--- /dev/null
@@ -0,0 +1,24 @@
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {HttpClientModule} from '@angular/common/http';
+import {CatalogCommonModule} from '@eg/share/catalog/catalog-common.module';
+import {LineitemModule} from '@eg/staff/acq/lineitem/lineitem.module';
+import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {RelatedRoutingModule} from './routing.module';
+import {RelatedComponent} from './related.component';
+
+@NgModule({
+  declarations: [
+    RelatedComponent
+  ],
+  imports: [
+    StaffCommonModule,
+    CatalogCommonModule,
+    LineitemModule,
+    HoldingsModule,
+    RelatedRoutingModule
+  ]
+})
+
+export class RelatedModule {
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/acq/related/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/acq/related/routing.module.ts
new file mode 100644 (file)
index 0000000..b4221bc
--- /dev/null
@@ -0,0 +1,41 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {LineitemListComponent} from '../lineitem/lineitem-list.component';
+import {LineitemDetailComponent} from '../lineitem/detail.component';
+import {LineitemCopiesComponent} from '../lineitem/copies.component';
+import {BriefRecordComponent} from '../lineitem/brief-record.component';
+import {LineitemHistoryComponent} from '../lineitem/history.component';
+import {LineitemWorksheetComponent} from '../lineitem/worksheet.component';
+import {RelatedComponent} from './related.component';
+
+const routes: Routes = [{
+  path: ':recordId',
+  component: RelatedComponent,
+  children : [{
+    path: '',
+    component: LineitemListComponent
+  }, {
+    path: 'brief-record',
+    component: BriefRecordComponent
+  }, {
+    path: 'lineitem/:lineitemId/detail',
+    component: LineitemDetailComponent
+  }, {
+    path: 'lineitem/:lineitemId/history',
+    component: LineitemHistoryComponent
+  }, {
+    path: 'lineitem/:lineitemId/items',
+    component: LineitemCopiesComponent
+  }, {
+    path: 'lineitem/:lineitemId/worksheet',
+    component: LineitemWorksheetComponent
+  }]
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+  providers: []
+})
+
+export class RelatedRoutingModule {}
index 7848bcc..0c9afce 100644 (file)
@@ -16,6 +16,10 @@ const routes: Routes = [{
   path: 'picklist',
   loadChildren: () =>
     import('./picklist/picklist.module').then(m => m.PicklistModule)
+}, {
+  path: 'related',
+  loadChildren: () =>
+    import('./related/related.module').then(m => m.RelatedModule)
 }];
 
 @NgModule({
index 810a470..1009410 100644 (file)
         href="/eg/staff/acq/legacy/lineitem/related/{{recId}}?target=bib">
         <span i18n>View/Place Orders</span>
       </a>
+      <!-- SETTING?
+      <a class="dropdown-item" routerLink="/staff/acq/related/{{recId}}">
+        <span i18n>View/Place Orders</span>
+      </a>
+      -->
     </div>
   </div>
 </div>
index cfc2fcc..e510286 100644 (file)
@@ -526,11 +526,21 @@ sub lineitems_related_by_bib {
         return scalar(@$results);
     } else {
         for my $result (@$results) {
-            # retrieve_lineitem takes care of POs and PLs and also handles
-            # options like flesh_notes and permissions checking.
-            $conn->respond(
-                retrieve_lineitem($self, $conn, $auth, $result->{"id"}, $options)
-            );
+
+            # Let retrieve_lineitem_impl handle the permission checks
+            # and fleshing where needed.
+            my $li = retrieve_lineitem_impl($e, $result->{id}, $options) 
+                or return $e->die_event;
+
+            if ($options->{idlist}) {
+                $conn->respond($li->id);
+
+            } else {
+                
+                # retrieve_lineitem takes care of POs and PLs and also handles
+                # options like flesh_notes and permissions checking.
+                $conn->respond($li);
+            }
         }
     }