From 8eed71301878cae7e1cd8ac286c0b9d3df1926fc Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 22 Jun 2022 16:18:17 -0400 Subject: [PATCH] LP1840773 SCKO Angular Signed-off-by: Bill Erickson --- .../src/eg2/src/app/scko/banner.component.html | 18 ++--- Open-ILS/src/eg2/src/app/scko/banner.component.ts | 60 +-------------- Open-ILS/src/eg2/src/app/scko/items.component.html | 33 ++++++++ Open-ILS/src/eg2/src/app/scko/items.component.ts | 89 ++++++++++++++++++++++ Open-ILS/src/eg2/src/app/scko/routing.module.ts | 4 + Open-ILS/src/eg2/src/app/scko/scko.component.css | 2 +- Open-ILS/src/eg2/src/app/scko/scko.component.html | 4 +- Open-ILS/src/eg2/src/app/scko/scko.module.ts | 2 + Open-ILS/src/eg2/src/app/scko/scko.service.ts | 77 ++++++++++++++++--- .../src/eg2/src/app/scko/summary.component.html | 21 +++-- Open-ILS/src/eg2/src/app/scko/summary.component.ts | 7 -- 11 files changed, 225 insertions(+), 92 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/scko/items.component.html create mode 100644 Open-ILS/src/eg2/src/app/scko/items.component.ts diff --git a/Open-ILS/src/eg2/src/app/scko/banner.component.html b/Open-ILS/src/eg2/src/app/scko/banner.component.html index d8b1f9ba21..037360a171 100644 --- a/Open-ILS/src/eg2/src/app/scko/banner.component.html +++ b/Open-ILS/src/eg2/src/app/scko/banner.component.html @@ -21,14 +21,14 @@ [(ngModel)]="patronUsername" name="patron-username" placeholder="Username or Barcode" i18n-placeholder> - - - - - + + + + +
@@ -48,7 +48,7 @@ + placeholder="Item Barcode..." i18n-placeholder> diff --git a/Open-ILS/src/eg2/src/app/scko/banner.component.ts b/Open-ILS/src/eg2/src/app/scko/banner.component.ts index a7cc682ee8..b6b311e482 100644 --- a/Open-ILS/src/eg2/src/app/scko/banner.component.ts +++ b/Open-ILS/src/eg2/src/app/scko/banner.component.ts @@ -42,6 +42,7 @@ export class SckoBannerComponent implements OnInit { ngOnInit() { + // TODO focus the right thing on page load const node = document.getElementById('staff-username'); // NOTE: Displaying a list of workstations will not work for users @@ -110,70 +111,13 @@ export class SckoBannerComponent implements OnInit { submitPatronLogin() { this.patronLoginFailed = false; - this.loadPatron().finally(() => { + this.scko.loadPatron(this.patronUsername, this.patronPassword).finally(() => { this.patronLoginFailed = this.scko.patronSummary === null; }); } - loadPatron(): Promise { - this.scko.resetPatron(); - - if (!this.patronUsername) { return; } - - let username; - let barcode; - - if (this.patronUsername.match(this.scko.barcodeRegex)) { - barcode = this.patronUsername; - } else { - username = this.patronUsername; - } - - if (this.scko.patronPasswordRequired) { - // TODO verify password - - return this.net.request( - 'open-ils.actor', - 'open-ils.actor.verify_user_password', - this.auth.token(), barcode, username, null, this.patronPassword) - - .toPromise().then(verified => { - if (Number(verified) === 1) { - return this.fetchPatron(username, barcode); - } else { - return Promise.reject('Bad password'); - } - }); - - } else { - - return this.fetchPatron(username, barcode); - } - } - - fetchPatron(username: string, barcode: string): Promise { - - return this.net.request( - 'open-ils.actor', - 'open-ils.actor.user.retrieve_id_by_barcode_or_username', - this.auth.token(), barcode, username).toPromise() - - .then(patronId => { - - const evt = this.evt.parse(patronId); - - if (evt || !patronId) { - console.error('Cannot find user: ', evt); - return Promise.reject('User not found'); - } - - return this.scko.loadPatron(patronId); - }); - } - submitItemBarcode() { console.log('Submitting barcode ', this.itemBarcode); } - } diff --git a/Open-ILS/src/eg2/src/app/scko/items.component.html b/Open-ILS/src/eg2/src/app/scko/items.component.html new file mode 100644 index 0000000000..e861623116 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/scko/items.component.html @@ -0,0 +1,33 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + +
BarcodeTitleAuthorDue DateRenewals LeftType
+ + + + {{circ.target_copy().barcode()}}{{getTitle(circ)}}{{getAuthor(circ)}}{{circ | egDueDate}}{{circ.renewal_remaining()}} + Renewal + Checkout +
+
diff --git a/Open-ILS/src/eg2/src/app/scko/items.component.ts b/Open-ILS/src/eg2/src/app/scko/items.component.ts new file mode 100644 index 0000000000..54a9186660 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/scko/items.component.ts @@ -0,0 +1,89 @@ +import {Component, OnInit, ViewEncapsulation} from '@angular/core'; +import {Router, ActivatedRoute, NavigationEnd} from '@angular/router'; +import {tap} from 'rxjs/operators'; +import {AuthService} from '@eg/core/auth.service'; +import {PcrudService} from '@eg/core/pcrud.service'; +import {NetService} from '@eg/core/net.service'; +import {IdlObject} from '@eg/core/idl.service'; +import {SckoService} from './scko.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; + +const CIRC_FLESH_DEPTH = 4; +const CIRC_FLESH_FIELDS = { + circ: ['target_copy'], + acp: ['call_number'], + acn: ['record'], + bre: ['flat_display_entries'] +}; + +@Component({ + templateUrl: 'items.component.html' +}) + +export class SckoItemsComponent implements OnInit { + + circs: IdlObject[] = []; + + constructor( + private router: Router, + private route: ActivatedRoute, + private net: NetService, + private auth: AuthService, + private pcrud: PcrudService, + public scko: SckoService + ) {} + + ngOnInit() { + + if (!this.scko.patronSummary) { + this.router.navigate(['/scko']); + return; + } + + this.net.request( + 'open-ils.actor', + 'open-ils.actor.user.checked_out.authoritative', + this.auth.token(), this.scko.patronSummary.id).toPromise() + + .then(data => { + const ids = data.out.concat(data.overdue).concat(data.long_overdue); + + return this.pcrud.search('circ', {id: ids}, { + flesh: CIRC_FLESH_DEPTH, + flesh_fields: CIRC_FLESH_FIELDS, + order_by : {circ : 'due_date'}, + + select: {bre : ['id']} + + }).pipe(tap(circ => { + this.circs.push(circ); + })).toPromise(); + }); + } + + isPrecat(copy: IdlObject): boolean { + return Number(copy.id()) === -1; + } + + displayValue(circ: IdlObject, field: string): string { + + const entry = + circ.target_copy().call_number().record().flat_display_entries() + .filter(e => e.name() === field)[0]; + + return entry ? entry.value() : ''; + } + + getTitle(circ: IdlObject): string { + const copy = circ.target_copy(); + if (this.isPrecat(copy)) { return copy.dummy_title(); } + return this.displayValue(circ, 'title'); + } + + getAuthor(circ: IdlObject): string { + const copy = circ.target_copy(); + if (this.isPrecat(copy)) { return copy.dummy_author(); } + return this.displayValue(circ, 'author'); + } +} + diff --git a/Open-ILS/src/eg2/src/app/scko/routing.module.ts b/Open-ILS/src/eg2/src/app/scko/routing.module.ts index 4c9ed2a8e2..02e4930e27 100644 --- a/Open-ILS/src/eg2/src/app/scko/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/scko/routing.module.ts @@ -2,6 +2,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; import {SckoComponent} from './scko.component'; import {SckoCheckoutComponent} from './checkout.component'; +import {SckoItemsComponent} from './items.component'; const routes: Routes = [{ path: '', @@ -9,6 +10,9 @@ const routes: Routes = [{ children: [{ path: '', component: SckoCheckoutComponent + }, { + path: 'items', + component: SckoItemsComponent }] }]; diff --git a/Open-ILS/src/eg2/src/app/scko/scko.component.css b/Open-ILS/src/eg2/src/app/scko/scko.component.css index 0ccf92c29f..c0348965d3 100644 --- a/Open-ILS/src/eg2/src/app/scko/scko.component.css +++ b/Open-ILS/src/eg2/src/app/scko/scko.component.css @@ -90,7 +90,7 @@ A { .oils-selfck-item-table td { text-align: left; - padding: 7px; + padding: 10px; } .oils-selfck-item-table thead { diff --git a/Open-ILS/src/eg2/src/app/scko/scko.component.html b/Open-ILS/src/eg2/src/app/scko/scko.component.html index a273be6e57..45df3e90b0 100644 --- a/Open-ILS/src/eg2/src/app/scko/scko.component.html +++ b/Open-ILS/src/eg2/src/app/scko/scko.component.html @@ -2,12 +2,12 @@
-
+
-
+
diff --git a/Open-ILS/src/eg2/src/app/scko/scko.module.ts b/Open-ILS/src/eg2/src/app/scko/scko.module.ts index e4b795d16c..b3823c4790 100644 --- a/Open-ILS/src/eg2/src/app/scko/scko.module.ts +++ b/Open-ILS/src/eg2/src/app/scko/scko.module.ts @@ -11,6 +11,7 @@ import {SckoService} from './scko.service'; import {SckoBannerComponent} from './banner.component'; import {SckoSummaryComponent} from './summary.component'; import {SckoCheckoutComponent} from './checkout.component'; +import {SckoItemsComponent} from './items.component'; @NgModule({ declarations: [ @@ -18,6 +19,7 @@ import {SckoCheckoutComponent} from './checkout.component'; SckoBannerComponent, SckoSummaryComponent, SckoCheckoutComponent, + SckoItemsComponent, ], imports: [ EgCommonModule, diff --git a/Open-ILS/src/eg2/src/app/scko/scko.service.ts b/Open-ILS/src/eg2/src/app/scko/scko.service.ts index a82e819032..e1b6c3a236 100644 --- a/Open-ILS/src/eg2/src/app/scko/scko.service.ts +++ b/Open-ILS/src/eg2/src/app/scko/scko.service.ts @@ -1,10 +1,11 @@ import {Injectable, EventEmitter} from '@angular/core'; +import {Router, ActivatedRoute, NavigationEnd} from '@angular/router'; +import {OrgService} from '@eg/core/org.service'; import {NetService} from '@eg/core/net.service'; import {AuthService} from '@eg/core/auth.service'; import {EventService, EgEvent} from '@eg/core/event.service'; import {IdlService, IdlObject} from '@eg/core/idl.service'; import {StoreService} from '@eg/core/store.service'; -import {ServerStoreService} from '@eg/core/server-store.service'; import {PatronService, PatronSummary, PatronStats} from '@eg/staff/share/patron/patron.service'; @Injectable({providedIn: 'root'}) @@ -18,9 +19,10 @@ export class SckoService { sessionCheckouts: any[] = []; constructor( + private route: ActivatedRoute, + private org: OrgService, private net: NetService, private evt: EventService, - private serverStore: ServerStoreService, public auth: AuthService, private patrons: PatronService, ) {} @@ -37,7 +39,9 @@ export class SckoService { .then(_ => { - return this.serverStore.getItemBatch([ + // Note we cannot use server-store unless we are logged + // in with a workstation. + return this.org.settings([ 'opac.barcode_regex', 'circ.selfcheck.patron_login_timeout', 'circ.selfcheck.auto_override_checkout_events', @@ -55,13 +59,65 @@ export class SckoService { this.barcodeRegex = new RegExp(regPattern); this.patronPasswordRequired = sets['circ.selfcheck.patron_password_required']; - console.log('REQ', this.patronPasswordRequired); + + // Load a patron by barcode via URL params. + // Useful for development. + const username = this.route.snapshot.queryParamMap.get('patron'); + + if (username && !this.patronPasswordRequired) { + return this.loadPatron(username); + } }); } - loadPatron(id: number): Promise { - return this.patrons.getFleshedById(id).then( - patron => this.patronSummary = new PatronSummary(patron)) + loadPatron(username: string, password?: string): Promise { + this.resetPatron(); + + if (!username) { return; } + + let barcode; + if (username.match(this.barcodeRegex)) { + barcode = username; + username = null; + } + + if (!this.patronPasswordRequired) { + return this.fetchPatron(username, barcode); + } + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.verify_user_password', + this.auth.token(), barcode, username, null, password) + + .toPromise().then(verified => { + if (Number(verified) === 1) { + return this.fetchPatron(username, barcode); + } else { + return Promise.reject('Bad password'); + } + }); + } + + fetchPatron(username: string, barcode: string): Promise { + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.user.retrieve_id_by_barcode_or_username', + this.auth.token(), barcode, username).toPromise() + + .then(patronId => { + + const evt = this.evt.parse(patronId); + + if (evt || !patronId) { + console.error('Cannot find user: ', evt); + return Promise.reject('User not found'); + } + + return this.patrons.getFleshedById(patronId); + }) + .then(patron => this.patronSummary = new PatronSummary(patron)) .then(_ => this.patrons.getVitalStats(this.patronSummary.patron)) .then(stats => this.patronSummary.stats = stats); } @@ -71,10 +127,13 @@ export class SckoService { } accountTotalCheckouts(): number { + // stats.checkouts.total_out includes claims returned + return this.sessionTotalCheckouts() + - this.patronSummary.stats.checkouts.total_out; + this.patronSummary.stats.checkouts.out + + this.patronSummary.stats.checkouts.overdue + + this.patronSummary.stats.checkouts.long_overdue; } - } diff --git a/Open-ILS/src/eg2/src/app/scko/summary.component.html b/Open-ILS/src/eg2/src/app/scko/summary.component.html index d6e06fd43f..20d22b09f2 100644 --- a/Open-ILS/src/eg2/src/app/scko/summary.component.html +++ b/Open-ILS/src/eg2/src/app/scko/summary.component.html @@ -39,14 +39,14 @@ Items Checked Out
Total items this session: - {{scko.sessionTotalCheckouts()}} + {{scko.sessionTotalCheckouts()}}
Total items on account: - {{scko.accountTotalCheckouts()}} + {{scko.accountTotalCheckouts()}}
@@ -54,10 +54,14 @@
Holds
- You have {{scko.patronSummary.stats.holds.ready}} item(s) ready for pickup + You have + {{scko.patronSummary.stats.holds.ready}} + item(s) ready for pickup.
- You have {{scko.patronSummary.stats.holds.total}} total holds + You have + {{scko.patronSummary.stats.holds.total}} + total holds.
Fines -
{{scko.patronSummary.stats.fines.balance_owed | currency}}
+
+ Total fines on account: + + {{scko.patronSummary.stats.fines.balance_owed | currency}} + +