LP1904036 checkin; more transit handling/slips
authorBill Erickson <berickxx@gmail.com>
Mon, 12 Apr 2021 15:39:12 +0000 (11:39 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:30 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/checkin/checkin.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts
Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.html
Open-ILS/src/eg2/src/app/staff/share/circ/route-dialog.component.ts
Open-ILS/src/sql/Pg/upgrade/XXXX.data.angular-patron.sql

index e3acc3c..8680b09 100644 (file)
@@ -15,9 +15,7 @@ import {BarcodeSelectComponent
     } from '@eg/staff/share/barcodes/barcode-select.component';
 
 interface CheckinGridEntry extends CheckinResult {
-    title?: string;
-    author?: string;
-    isbn?: string;
+    // May need to extend...
 }
 
 @Component({
@@ -84,23 +82,17 @@ export class CheckinComponent implements OnInit, AfterViewInit {
         })
 
         .then((result: CheckinResult) => {
-            if (result) {
-                this.dispatchResult(result);
-                return result;
+            if (result && result.success) {
+                this.gridifyResult(result);
             }
+            delete this.copiesInFlight[this.barcode];
+            this.resetForm();
+            return result;
         })
 
         .finally(() => delete this.copiesInFlight[this.barcode]);
     }
 
-    dispatchResult(result: CheckinResult) {
-        if (result.success) {
-            this.gridifyResult(result);
-            this.resetForm();
-            return;
-        }
-    }
-
     collectParams(): Promise<CheckinParams> {
 
         const params: CheckinParams = {
index 124ef24..d5bdcb5 100644 (file)
@@ -146,23 +146,17 @@ export class CheckoutComponent implements OnInit, AfterViewInit {
         })
 
         .then((result: CheckoutResult) => {
-            if (result) {
-                this.dispatchResult(result);
-                return result;
+            if (result && result.success) {
+                this.gridifyResult(result);
             }
+            delete this.copiesInFlight[barcode];
+            this.resetForm();
+            return result;
         })
 
         .finally(() => delete this.copiesInFlight[barcode]);
     }
 
-    dispatchResult(result: CheckoutResult) {
-        if (result.success) {
-            this.gridifyResult(result);
-            this.resetForm();
-            return;
-        }
-    }
-
     resetForm() {
 
         if (this.dueDateOptions < 2) {
index f667fc1..829298f 100644 (file)
@@ -169,9 +169,16 @@ export interface CheckinResult {
     record?: IdlObject;
     hold?: IdlObject;
     transit?: IdlObject;
-    org?: number;
     patron?: IdlObject;
-    routeTo?: string;
+
+    // Calculated values
+    routeTo?: string; // org name or in-branch destination
+    title?: string;
+    author?: string;
+    isbn?: string;
+    destOrg?: IdlObject;
+    destAddress?: IdlObject;
+    destCourierCode?: string;
 }
 
 @Injectable()
@@ -245,7 +252,7 @@ export class CircService {
 
     getOrgAddr(orgId: number, addrType): Promise<IdlObject> {
         const org = this.org.get(orgId);
-        const addrId = this.org[addrType]();
+        const addrId = this.org[addrType];
 
         if (!addrId) { return Promise.resolve(null); }
 
@@ -563,7 +570,18 @@ export class CircService {
         const circ = result.circ;
         const parent_circ = result.parent_circ;
 
-        let promise = Promise.resolve();;
+        let promise = Promise.resolve();
+
+        if (result.record) {
+            result.title = result.record.title();
+            result.author = result.record.author();
+            result.isbn = result.record.isbn();
+
+        } else if (result.copy) {
+            result.title = result.copy.dummy_title();
+            result.author = result.copy.dummy_author();
+            result.isbn = result.copy.dummy_isbn();
+        }
 
         if (copy) {
             if (this.copyLocationCache[copy.location()]) {
index f2486a3..ec18c50 100644 (file)
@@ -1,36 +1,11 @@
 
 <!-- 
   Two separate dialogs are defined within, one for the holds slip
-  and one for hold and non-hold transits.
+  and one for hold and non-hold transits.  They share a number of
+  sub-templates.
 -->
 
-<ng-template #holdShelfTmpl>
-  <div *ngIf="checkin.hold.behind_desk() == 't'" i18n>
-    This item should be routed to the <strong>Private Holds Shelf</strong>
-  </div>
-  <div *ngIf="checkin.hold.behind_desk() == 'f'" i18n>
-    This item should be routed to the <strong>Public Holds Shelf</strong>
-  </div>
-
-  <br/>
-  <div>
-    <span class="mr-2" i18n>Item Barcode:</span>
-    <span>{{checkin.copy.barcode()}}</span>
-  </div>
-  <div>
-    <span class="mr-2" i18n>Title:</span>
-    <span>{{checkin.record.title()}}</span>
-  </div>
-  <div>
-    <span class="mr-2" i18n>Author:</span>
-    <span>{{checkin.record.author()}}</span>
-  </div>
-  <div>
-    <span class="mr-2" i18n>Call Number:</span>
-    <span>{{checkin.volume.prefix().label()}}&nbsp;{{checkin.volume.label()}}&nbsp;{{checkin.volume.suffix().label()}}</span>
-  </div>
-  <br/>
-
+<ng-template #patronSummary>
   <div *ngIf="checkin.patron.alias()">
     Hold for patron {{checkin.patron.alias()}}
   </div>
     <span class="mr-2" i18n>Patron Barcode:</span>
     <span>{{checkin.patron.card().barcode()}}</span>
   </div>
-  <div *ngIf="checkin.hold.phone_notify()" i18n>Notify by phone: {{checkin.hold.phone_notify()}}</div>
-  <div *ngIf="checkin.hold.sms_notify()" i18n>Notify by text: {{checkin.hold.sms_notify()}}</div>
-  <div *ngIf="checkin.hold.email_notify() == 't'" i18n>Notify by email: {{checkin.patron.email()}}</div>
-  <br/>
+</ng-template>
+
+<ng-template #itemSummary>
+  <div>
+    <span class="mr-2" i18n>Item Barcode:</span>
+    <span>{{checkin.copy.barcode()}}</span>
+  </div>
+  <div>
+    <span class="mr-2" i18n>Title:</span>
+    <span>{{checkin.title}}</span>
+  </div>
+  <div>
+    <span class="mr-2" i18n>Author:</span>
+    <span>{{checkin.author}}</span>
+  </div>
+  <div *ngIf="checkin.volume">
+    <span class="mr-2" i18n>Call Number:</span>
+    <span>{{checkin.volume.prefix().label()}}&nbsp;{{
+                       checkin.volume.label()}}&nbsp;{{checkin.volume.suffix().label()}}</span>
+  </div>
+</ng-template>
+
+<ng-template #holdSummary>
   <div>
     <span class="mr-2" i18n>Request Date:</span>
     <span>{{checkin.hold.request_time() | date:'short'}}</span>
   </div>
-  <div>
+  <div *ngIf="checkin.hold.notes().length > 0">
     <span class="mr-2" i18n>Request Notes:</span>
     <ul>
       <li *ngFor="let note of checkin.hold.notes()">
       </li>
     </ul>
   </div>
+</ng-template>
+
+<ng-template #holdShelfTmpl>
+  <div *ngIf="checkin.hold.behind_desk() == 't'" i18n>
+    This item should be routed to the <strong>Private Holds Shelf</strong>
+  </div>
+  <div *ngIf="checkin.hold.behind_desk() == 'f'" i18n>
+    This item should be routed to the <strong>Public Holds Shelf</strong>
+  </div>
+  <br/>
+
+  <ng-container *ngTemplateOutlet="itemSummary"></ng-container><br/>
+  <ng-container *ngTemplateOutlet="patronSummary"></ng-container><br/>
+
+  <!-- in hold shelf mode, we need to specify the notification prefs -->
+  <div *ngIf="checkin.hold.phone_notify()" i18n>Notify by phone: {{checkin.hold.phone_notify()}}</div>
+  <div *ngIf="checkin.hold.sms_notify()" i18n>Notify by text: {{checkin.hold.sms_notify()}}</div>
+  <div *ngIf="checkin.hold.email_notify() == 't'" i18n>Notify by email: {{checkin.patron.email()}}</div>
+
+  <ng-container *ngTemplateOutlet="holdSummary"></ng-container>
+  <br/>
   <div>
     <span class="mr-2" i18n>Slip Date:</span>
     <span>{{today | date:'short'}}</span>
 </ng-template>
 
 <ng-template #transitTmpl>
+  <div>
+    <span class="mr-2" i18n>checkin.destination</span>
+    <strong>{{checkin.destOrg.shortname()}}</strong>
+  </div>
+  <div *ngIf="checkin.destCourierCode">{{checkin.destCourierCode}} </div>
+  <br/>
+  <div>
+    <address>
+      <strong>{{checkin.destOrg.name()}}</strong><br>
+      <span *ngIf="checkin.destAddress">
+        {{checkin.destAddress.street1()}} {{checkin.destAddress.street2()}}<br/>
+        {{checkin.destAddress.city()}}, {{checkin.destAddress.state()}} {{checkin.destAddress.post_code()}}
+      </span>
+      <span *ngIf="!checkin.destAddress" i18n>
+        We do not have a holds address for this library.
+      </span>
+      <br/>
+      <abbr title="Phone">P:</abbr> {{checkin.destOrg.phone()}}
+    </address>
+  </div>
+  <ng-container *ngTemplateOutlet="itemSummary"></ng-container>
+  <br/>
+  <div *ngIf="checkin.patron">
+    <ng-container *ngTemplateOutlet="patronSummary"></ng-container>
+    <br/>
+    <ng-container *ngTemplateOutlet="holdSummary"></ng-container>
+    <br/>
+  </div>
+  <div>
+    <span class="mr-2" i18n>Slip Date:</span>
+    <span>{{today | date:'short'}}</span>
+  </div>
 </ng-template>
 
 <ng-template #dialogContent>
   <div class="modal-header bg-info">
     <h4>
-      <ng-container *ngIf="slip == 'hold_shelf_slip'" i18n>Hold Slip</ng-container>
-      <ng-container *ngIf="slip != 'hold_shelf_slip'" i18n>Transit Slip</ng-container>
+      <ng-container *ngIf="slip == 'hold_shelf_slip'">
+        <strong i18n>Hold Slip</strong>
+        <img class="p-2" src="/images/portal/holds.png" i18n-alt alt="holds icon"/>
+      </ng-container>
+      <ng-container *ngIf="slip != 'hold_shelf_slip'">
+        <strong i18n>Transit Slip</strong>
+        <img class="p-2" src="/images/transit.png" i18n-alt alt="transit van icon"/>
+      </ng-container>
     </h4>
     <button type="button" class="close"
       i18n-aria-label aria-label="Close" (click)="close()">
index 36edfd5..9328df3 100644 (file)
@@ -25,9 +25,6 @@ export class RouteDialogComponent extends DialogComponent {
     checkin: CheckinResult;
     noAutoPrint: {[template: string]: boolean} = {};
     slip: string;
-    orgAddress: IdlObject;
-    destCourierCode: string;
-    destOrg: IdlObject;
     today = new Date();
 
     constructor(
@@ -69,16 +66,26 @@ export class RouteDialogComponent extends DialogComponent {
         let promise = Promise.resolve(null);
         const hold = this.checkin.hold;
 
-        if (this.checkin.org && this.slip !== 'hold_shelf_slip') {
+        if (this.slip !== 'hold_shelf_slip') {
 
-            promise = promise.then(_ => {
-                return this.circ.getOrgAddr(this.checkin.org, 'holds_address')
-                .then(addr => this.orgAddress = addr);
-            });
+            // Always fetch the most recent transit for the copy,
+            // regardless of what data the server returns in the payload.
+
+            promise = promise.then(_ => this.circ.findCopyTransit(this.checkin))
+            .then(transit => {
+                this.checkin.transit = transit;
+                this.checkin.destOrg = this.org.get(transit.dest());
+                return this.circ.getOrgAddr(this.checkin.destOrg.id(), 'holds_address');
+            })
+            .then(addr => {
+                this.checkin.destAddress = addr;
+                return this.org.settings('lib.courier_code', this.checkin.destOrg.id())
+            })
+
+            .then(sets => this.checkin.destCourierCode = sets['lib.courier_code']);
         }
 
         if (hold) {
-
             promise = promise.then(_ => {
                 return this.pcrud.retrieve('au', hold.usr(),
                     {flesh: 1, flesh_fields : {'au' : ['card']}}).toPromise()
@@ -86,20 +93,6 @@ export class RouteDialogComponent extends DialogComponent {
             });
         }
 
-        if (this.slip !== 'hold_shelf_slip') {
-
-            promise = promise.then(_ => this.circ.findCopyTransit(this.checkin))
-            .then(transit => {
-                this.checkin.transit = transit;
-                return this.org.settings('lib.courier_code', transit.dest().id())
-                .then(sets => this.destCourierCode = sets['lib.courier_code']);
-            });
-        }
-
-        if (this.checkin.transit) {
-            this.destOrg = this.org.get(this.checkin.transit.dest());
-        }
-
         this.audio.play(hold ?
             'info.checkin.transit.hold' : 'info.checkin.transit');
 
index be025b8..68e4ece 100644 (file)
@@ -287,7 +287,6 @@ INSERT INTO config.print_template
     (name, label, owner, active, locale, content_type, template)
 VALUES ('hold_shelf_slip', 'Hold Shelf Slip', 1, TRUE, 'en-US', 'text/html', '');
 
-*/
 
 UPDATE config.print_template SET template = $TEMPLATE$
 [% 
@@ -311,7 +310,7 @@ UPDATE config.print_template SET template = $TEMPLATE$
 <br/>
 
 <div>Barcode: [% copy.barcode %]</div>
-<div>Title: [% record.title %]</div>
+<div>Title: [% checkin.title %]</div>
 <div>Call Number: [% volume.prefix.label %] [% volume.label %] [% volume.suffix.label %]</div>
 
 <br/>
@@ -347,7 +346,103 @@ UPDATE config.print_template SET template = $TEMPLATE$
 </div>
 
 $TEMPLATE$ WHERE name = 'hold_shelf_slip';
+
+
+INSERT INTO config.print_template 
+    (name, label, owner, active, locale, content_type, template)
+VALUES ('transit_slip', 'Transit Slip', 1, TRUE, 'en-US', 'text/html', '');
+
+
+UPDATE config.print_template SET template = $TEMPLATE$
+[% 
+  USE date;
+  USE money = format('$%.2f');
+  SET checkin = template_data.checkin;
+  SET copy = checkin.copy;
+  SET destOrg = checkin.destOrg;
+  SET destAddress = checkin.destAddress;
+  SET destCourierCode = checkin.destCourierCode;
+%] 
+<div>
+  <div>This item needs to be routed to <b>[% destOrg.shortname %]</b></div>
+  <div>[% destOrg.name %]</div>
+  [% IF destCourierCode %]Courier Code: [% destCourierCode %][% END %]
+
+  [% IF destAddress %]
+    <div>[% destAddress.street1 %]</div>
+    <div>[% destAddress.street2 %]</div>
+    <div>[% destAddress.city %],
+    [% destAddress.state %]
+    [% destAddress.post_code %]</div>
+  [% ELSE %]
+    <div>We do not have a holds address for this library.</div>
+  [% END %]
+  
+  <br/>
+  <div>Barcode: [% copy.barcode %]</div>
+  <div>Title: [% checkin.title %]</div>
+  <div>Author: [% checkin.author %]</div>
+  
+  <br/>
+  <div>Slip Date: [% date.format(date.now, '%x %r') %]</div>
+  <div>Printed by [% staff.first_given_name %] at [% staff_org.shortname %]</div>
+</div>
+
+$TEMPLATE$ WHERE name = 'transit_slip';
+
+*/
+
  
+INSERT INTO config.print_template 
+    (name, label, owner, active, locale, content_type, template)
+VALUES ('hold_transit_slip', 'Hold Transit Slip', 1, TRUE, 'en-US', 'text/html', '');
+
+UPDATE config.print_template SET template = $TEMPLATE$
+[% 
+  USE date;
+  USE money = format('$%.2f');
+  SET checkin = template_data.checkin;
+  SET copy = checkin.copy;
+  SET hold = checkin.hold;
+  SET patron = checkin.patron;
+  SET destOrg = checkin.destOrg;
+  SET destAddress = checkin.destAddress;
+  SET destCourierCode = checkin.destCourierCode;
+%] 
+<div>
+  <div>This item needs to be routed to <b>[% destOrg.shortname %]</b></div>
+  <div>[% destOrg.name %]</div>
+  [% IF destCourierCode %]Courier Code: [% destCourierCode %][% END %]
+
+  [% IF destAddress %]
+    <div>[% destAddress.street1 %]</div>
+    <div>[% destAddress.street2 %]</div>
+    <div>[% destAddress.city %],
+    [% destAddress.state %]
+    [% destAddress.post_code %]</div>
+  [% ELSE %]
+    <div>We do not have a holds address for this library.</div>
+  [% END %]
+  
+  <br/>
+  <div>Barcode: [% copy.barcode %]</div>
+  <div>Title: [% checkin.title %]</div>
+  <div>Author: [% checkin.author %]</div>
+
+  <br/>
+  <div>Hold for patron [% patron.card.barcode %]</div>
+  
+  <br/>
+  <div>Request Date: [% 
+    date.format(helpers.format_date(hold.request_time, staff_org_timezone), '%x %r') %]
+  </div>
+  <div>Slip Date: [% date.format(date.now, '%x %r') %]</div>
+  <div>Printed by [% staff.first_given_name %] at [% staff_org.shortname %]</div>
+</div>
+
+$TEMPLATE$ WHERE name = 'transit_slip';
+
+
 COMMIT;