[(ngModel)]="patronUsername" name="patron-username"
placeholder="Username or Barcode" i18n-placeholder>
- <label class="sr-only" for="patron-password" i18n>Password</label>
-
- <input type="password" class="form-control shadow border border-dark rounded ml-2"
- autocomplete="off" id="patron-password" required
- [(ngModel)]="patronPassword" name="patron-password"
- placeholder="Password" i18n-placeholder>
-
- <button type="submit" class="btn btn-dark ml-2">Submit</button>
+ <ng-container *ngIf="scko.patronPasswordRequired">
+ <label class="sr-only" for="patron-password" i18n>Password</label>
+
+ <input type="password" class="form-control shadow border border-dark rounded ml-2"
+ autocomplete="off" id="patron-password" required
+ [(ngModel)]="patronPassword" name="patron-password"
+ placeholder="Password" i18n-placeholder>
+ </ng-container>
</form>
</div>
<div class="flex-1"></div>
<input type="text" class="form-control border border-dark shadow-rounded"
autocomplete="off" id="item-barcode" required
[(ngModel)]="itemBarcode" name="item-barcode"
- placeholder="Item Barcode" i18n-placeholder>
+ placeholder="Item Barcode..." i18n-placeholder>
</form>
</div>
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
submitPatronLogin() {
this.patronLoginFailed = false;
- this.loadPatron().finally(() => {
+ this.scko.loadPatron(this.patronUsername, this.patronPassword).finally(() => {
this.patronLoginFailed = this.scko.patronSummary === null;
});
}
- loadPatron(): Promise<any> {
- 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<any> {
-
- 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);
}
-
}
--- /dev/null
+<div id='oils-selfck-circ-table-div'>
+ <table id='oils-selfck-circ-table' class='oils-selfck-item-table'>
+ <thead>
+ <tr>
+ <td class="rounded-left" id='oils-self-circ-pic-cell'></td>
+ <td i18n>Barcode</td>
+ <td i18n>Title</td>
+ <td i18n>Author</td>
+ <td i18n>Due Date</td>
+ <td i18n>Renewals Left</td>
+ <td class="rounded-right" i18n>Type</td>
+ </tr>
+ </thead>
+ <tbody id='oils-selfck-circ-out-tbody' class='oils-selfck-item-table'>
+ <tr *ngFor="let circ of circs">
+ <td>
+ <ng-container *ngIf="circ.target_copy().id() != -1">
+ <img src="/opac/extras/ac/jacket/small/r/{{circ.target_copy().call_number().record().id()}}"/>
+ </ng-container>
+ </td>
+ <td>{{circ.target_copy().barcode()}}</td>
+ <td>{{getTitle(circ)}}</td>
+ <td>{{getAuthor(circ)}}</td>
+ <td>{{circ | egDueDate}}</td>
+ <td>{{circ.renewal_remaining()}}</td>
+ <td>
+ <span *ngIf="circ.parent_circ()" i18n>Renewal</span>
+ <span *ngIf="!circ.parent_circ()" i18n>Checkout</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
--- /dev/null
+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');
+ }
+}
+
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: '',
children: [{
path: '',
component: SckoCheckoutComponent
+ }, {
+ path: 'items',
+ component: SckoItemsComponent
}]
}];
.oils-selfck-item-table td {
text-align: left;
- padding: 7px;
+ padding: 10px;
}
.oils-selfck-item-table thead {
<eg-scko-banner></eg-scko-banner>
<div *ngIf="scko.auth.token() && scko.patronSummary" class="row mt-5">
- <div class="col-lg-8">
+ <div class="col-lg-9">
<div class="ml-2 scko-page">
<router-outlet></router-outlet>
</div>
</div>
- <div class="col-lg-4"><eg-scko-summary></eg-scko-summary></div>
+ <div class="col-lg-3"><eg-scko-summary></eg-scko-summary></div>
</div>
<!-- global toast alerts -->
import {SckoBannerComponent} from './banner.component';
import {SckoSummaryComponent} from './summary.component';
import {SckoCheckoutComponent} from './checkout.component';
+import {SckoItemsComponent} from './items.component';
@NgModule({
declarations: [
SckoBannerComponent,
SckoSummaryComponent,
SckoCheckoutComponent,
+ SckoItemsComponent,
],
imports: [
EgCommonModule,
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'})
sessionCheckouts: any[] = [];
constructor(
+ private route: ActivatedRoute,
+ private org: OrgService,
private net: NetService,
private evt: EventService,
- private serverStore: ServerStoreService,
public auth: AuthService,
private patrons: PatronService,
) {}
.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',
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<any> {
- return this.patrons.getFleshedById(id).then(
- patron => this.patronSummary = new PatronSummary(patron))
+ loadPatron(username: string, password?: string): Promise<any> {
+ 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<any> {
+
+ 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);
}
}
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;
}
-
}
<legend i18n>Items Checked Out</legend>
<div>
<span i18n>Total items this session: </span>
- <span>{{scko.sessionTotalCheckouts()}}</span>
+ <span class="font-weight-bold">{{scko.sessionTotalCheckouts()}}</span>
</div>
<div class="mt-2">
<span i18n>Total items on account: </span>
- <span>{{scko.accountTotalCheckouts()}}</span>
+ <span class="font-weight-bold">{{scko.accountTotalCheckouts()}}</span>
</div>
<div class="mt-2">
- <a (click)="null">
+ <a routerLink="/scko/items">
<button type="button" class="scko-button" i18n>View Items Out</button>
</a>
</div>
<fieldset>
<legend i18n>Holds</legend>
<div i18n>
- You have {{scko.patronSummary.stats.holds.ready}} item(s) ready for pickup
+ You have
+ <span class="font-weight-bold">{{scko.patronSummary.stats.holds.ready}}</span>
+ item(s) ready for pickup.
</div>
<div class="mt-2" i18n>
- You have {{scko.patronSummary.stats.holds.total}} total holds
+ You have
+ <span class="font-weight-bold">{{scko.patronSummary.stats.holds.total}}</span>
+ total holds.
</div>
<div class="mt-2">
<a href='javascript:void(0);' id='oils-selfck-hold-details-link'>
</fieldset>
<fieldset>
<legend i18n>Fines</legend>
- <div>{{scko.patronSummary.stats.fines.balance_owed | currency}}</div>
+ <div>
+ <span i18n>Total fines on account:</span>
+ <span class="font-weight-bold">
+ {{scko.patronSummary.stats.fines.balance_owed | currency}}
+ </span>
+ </div>
<div class="mt-2">
<span>
<a href='javascript:void(0);' id='oils-selfck-view-fines-link'>
receiptType = 'email';
constructor(
- private route: ActivatedRoute,
- private store: StoreService,
- private net: NetService,
- private auth: AuthService,
- private evt: EventService,
- private ngLocation: Location,
- private org: OrgService,
public scko: SckoService
) {}