Skip to content

Commit

Permalink
patron: add 'blocked' functionnality
Browse files Browse the repository at this point in the history
* Displays on patron detail view if it is blocked. Displays the blocked reason.
* Adds 2 fields on patron detail view:
  * blocked (if patron is blocked)
  * blocked reason (why this patron was blocked)
* Patron loan detailed view: add a blocked banner if patron is blocked.
* Adds different notification to librarian if a patron is blocked while
asking for new request

Co-Authored-by: Olivier DOSSMANN <git@dossmann.net>
  • Loading branch information
blankoworld committed May 12, 2020
1 parent 8bbb03a commit a48d0a5
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
</admin-circulation-patron-detailed>
</div>
</div>

<div class="row" *ngIf="patronInfo | patronBlockedMessage as message">
<div class="col">
<div class="alert alert-danger" role="alert">{{ message }}</div>
</div>
</div>

<div class="row">
<div class="col-md-12">
<admin-circulation-items-list
Expand Down
28 changes: 20 additions & 8 deletions projects/admin/src/app/circulation/patron/loan/loan.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import { PatronService } from '../../../service/patron.service';
import { UserService } from '../../../service/user.service';
import { Item, ItemAction, ItemStatus } from '../../items';
import { ItemsService } from '../../items.service';
import { PatronBlockedMessagePipe } from '../../../pipe/patron-blocked-message.pipe';

@Component({
selector: 'admin-loan',
templateUrl: './loan.component.html'
templateUrl: './loan.component.html',
providers: [PatronBlockedMessagePipe]
})
export class LoanComponent implements OnInit {
public placeholder: string = this._translate.instant(
Expand Down Expand Up @@ -66,7 +68,8 @@ export class LoanComponent implements OnInit {
private _translate: TranslateService,
private _toastService: ToastrService,
private _patronService: PatronService,
private _userService: UserService
private _userService: UserService,
private _patronBlockedMessagePipe: PatronBlockedMessagePipe
) {
}

Expand Down Expand Up @@ -202,14 +205,23 @@ export class LoanComponent implements OnInit {
},
err => {
let errorMessage = '';
if (err && err.error && err.error.status) {
errorMessage = err.error.status;
if (err && err.error && err.error.message) {
errorMessage = err.error.message;
}
if (err.error.status === 403) {
this._toastService.error(
this._translate.instant('Checkout is not allowed by circulation policy'),
this._translate.instant('Checkout')
);
// Specific case when user is blocked (for better user comprehension)
if (errorMessage !== '' && errorMessage.startsWith('BLOCKED USER')) {
const blockedMessage = this._patronBlockedMessagePipe.transform(this.patron);
this._toastService.error(
`${this._translate.instant('Checkout not possible.')} ${blockedMessage}`,
this._translate.instant('Circulation')
);
} else {
this._toastService.error(
this._translate.instant('Checkout is not allowed by circulation policy'),
this._translate.instant('Checkout')
);
}
} else {
this._toastService.error(
this._translate.instant('An error occured on the server: ') + errorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
</div>
</div>

<div class="col mb-4" *ngIf="patron | patronBlockedMessage as message">
<div class="alert alert-danger" role="alert">
{{ message }}
</div>
</div>

<div class="col">
<ul class="nav nav-tabs">
<li class="nav-item">
Expand Down
22 changes: 20 additions & 2 deletions projects/admin/src/app/class/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export class User {
city: string;
email: string;
first_name: string;
is_patron: boolean;
last_name: string;
library: Library;
name: string;
Expand All @@ -37,9 +36,11 @@ export class User {
barcode?: string;
items?: any[];
patron_type?: PatronType;
is_logged = false;
displayPatronMode = true;
currentLibrary: string;
// When patron is blocked, add 'blocked' and 'blocked_note' fields.
blocked = false;
blocked_note: string;

static readonly STORAGE_KEY = 'user';

Expand Down Expand Up @@ -91,6 +92,23 @@ export class User {
getCurrentLibrary() {
return this.currentLibrary;
}

/**
* Is this user a patron?
* @return a boolean
*/
get isPatron() {
return this.hasRole('patron');
}

/**
* Is this user a librarian?
* @return a boolean
*/
get isLibrarian() {
return this.hasRole('librarian');
}

}

export interface Organisation {
Expand Down
30 changes: 30 additions & 0 deletions projects/admin/src/app/pipe/patron-blocked-message.pipe.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* RERO ILS UI
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { PatronBlockedMessagePipe } from './patron-blocked-message.pipe';
import { TranslateService } from '@ngx-translate/core';

class TranslateServiceMock {
currentLang = 'en';
}

describe('PatronBlockedMessagePipe', () => {
it('create an instance', () => {
const pipe = new PatronBlockedMessagePipe(new TranslateServiceMock() as TranslateService);
expect(pipe).toBeTruthy();
});
});
43 changes: 43 additions & 0 deletions projects/admin/src/app/pipe/patron-blocked-message.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* RERO ILS UI
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';


@Pipe({
name: 'patronBlockedMessage'
})
export class PatronBlockedMessagePipe implements PipeTransform {

/**
* Constructor
* @param translate - TranslateService
*/
constructor(private translate: TranslateService) {}

/**
* Display a message if patron is blocked
* @param patron - Patron object
*/
transform(patron: any, ...args: any[]): any {
if (patron == null || patron.blocked !== true) {
return null;
}
return `${this.translate.instant('This patron is currently blocked.')} ${this.translate.instant('Reason')}: ${patron.blocked_note}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ <h5 class="card-title">{{ patron.last_name }}, {{ patron.first_name }}</h5>
{{ patron.postal_code }} {{ patron.city }}<br>
{{ patron.email }}
</p>
<div *ngIf="patron | patronBlockedMessage as message" class="alert alert-danger" role="alert">
{{ message }}
</div>
</div>
</div>
<form [formGroup]="form" (ngSubmit)="submit(model)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,48 @@
 along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<ng-container *ngIf="record$ | async as record">
<h1 class="mb-3">{{ record.metadata.last_name }} {{ record.metadata.first_name }}</h1>
<ng-container *ngIf="patron">
<h1 class="mb-3">{{ patron.last_name }} {{ patron.first_name }}</h1>
<!-- BLOCKED OR NOT? -->
<div *ngIf="patron | patronBlockedMessage as message" class="alert alert-danger" role="alert">
{{ message }}
</div>
<article>
<section class="m-2 p-2">
<!-- BARCODE -->
<dl class="row mb-0" *ngIf="record.metadata.barcode">
<dl class="row mb-0" *ngIf="patron.barcode">
<dt class="col-sm-3 offset-sm-2 offset-md-0">
{{ 'Barcode' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
<a [routerLink]="['/circulation', 'patron', record.metadata.barcode]">{{ record.metadata.barcode }}</a>
<a [routerLink]="['/circulation', 'patron', patron.barcode]">{{ patron.barcode }}</a>
</dd>
</dl>
<!-- TYPE -->
<dl class="row mb-0" *ngIf="hasRole('patron')">
<dl class="row mb-0" *ngIf="patron.isPatron">
<dt class="col-sm-3 offset-sm-2 offset-md-0">
{{ 'Type' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.patron_type.pid | getRecord: 'patron_types' : 'field' : 'name' | async }}
{{ patron.patron_type.pid | getRecord: 'patron_types' : 'field' : 'name' | async }}
</dd>
</dl>
<!-- TYPE -->
<dl class="row mb-0" *ngIf="hasRole('librarian')">
<dl class="row mb-0" *ngIf="patron.isLibrarian">
<dt class="col-sm-3 offset-sm-2 offset-md-0">
{{ 'Library' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.library.pid | getRecord: 'libraries' : 'field' : 'name' | async }}
{{ patron.library.pid | getRecord: 'libraries' : 'field' : 'name' | async }}
</dd>
</dl>
<!-- PHONE -->
<dl class="row mb-0" *ngIf="record.metadata.phone">
<dl class="row mb-0" *ngIf="patron.phone">
<dt class="col-sm-3 offset-sm-2 offset-md-0">
{{ 'Phone' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.phone }}
{{ patron.phone }}
</dd>
</dl>
<!-- EMAIL -->
Expand All @@ -61,7 +65,7 @@ <h1 class="mb-3">{{ record.metadata.last_name }} {{ record.metadata.first_name }
{{ 'Email' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.email }}
{{ patron.email }}
</dd>
</dl>
<!-- STREET -->
Expand All @@ -70,7 +74,7 @@ <h1 class="mb-3">{{ record.metadata.last_name }} {{ record.metadata.first_name }
{{ 'Street' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.street }}
{{ patron.street }}
</dd>
</dl>
<!-- CITY -->
Expand All @@ -79,17 +83,17 @@ <h1 class="mb-3">{{ record.metadata.last_name }} {{ record.metadata.first_name }
{{ 'City' | translate }}:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
{{ record.metadata.postal_code }} {{ record.metadata.city }}
{{ patron.postal_code }} {{ patron.city }}
</dd>
</dl>
<!-- ROLES -->
<dl class="row mb-0">
<dt class="col-sm-3 offset-sm-2 offset-md-0">
<ng-container *ngIf="record.metadata.roles.length === 1; else roles" translate>Role</ng-container>
<ng-container *ngIf="patron.roles.length === 1; else roles" translate>Role</ng-container>
<ng-template #roles translate>Roles</ng-template>:
</dt>
<dd class="col-sm-7 col-md-8 mb-0">
<ng-container *ngFor="let role of record.metadata.roles; let last=last">
<ng-container *ngFor="let role of patron.roles; let last=last">
{{ role | translate }}{{ last ? '' : ', ' }}
</ng-container>
</dd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,42 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { DetailRecord } from '@rero/ng-core/lib/record/detail/view/detail-record';
import { Observable, Subscription } from 'rxjs';
import { PatronService } from '../../../service/patron.service';
import { User } from '../../../class/user';

@Component({
selector: 'admin-patron-detail-view',
templateUrl: './patron-detail-view.component.html'
})
export class PatronDetailViewComponent implements OnInit, DetailRecord, OnDestroy {

/** Observable resolving record data */
/** Data from patron we received */
record$: Observable<any>;

/** Record subscription */
private _recordObs: Subscription;

/** Document record */
record: any;
/** Current displayed/used patron */
patron: User;

/** record type */
type: string;

/** Constructor
* @param _patronService : PatronService
*/
constructor(private _patronService: PatronService) { }
/** Subscription to (un)follow the record$ Observable */
private _subscription$ = new Subscription();

/**
* Init
* Current patron initialization.
*/
ngOnInit() {
this._recordObs = this.record$.subscribe(record => {
this.record = record;
this._patronService.setRecord(record);
this._subscription$ = this.record$.subscribe(record => {
this.patron = new User(record.metadata);
});
}

/**
* Destroy
*/
ngOnDestroy(): void {
this._recordObs.unsubscribe();
}

/** Check if the current logged user has a specific role
* @return True | False depending if the current logged user has the desired role
* Unsubscribe observable when destroying the PatronDetailViewComponent
*/
hasRole(role: string): boolean {
return this._patronService.hasRole(role);
ngOnDestroy() {
this._subscription$.unsubscribe();
}
}
9 changes: 8 additions & 1 deletion projects/admin/src/app/routes/patrons-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ export class PatronsRoute extends BaseRoute implements RouteInterface {
detailComponent: PatronDetailViewComponent,
canUpdate: (record: any) => this._routeToolService.canUpdate(record, this.recordType),
canDelete: (record: any) => this._routeToolService.canDelete(record, this.recordType),
aggregationsExpand: ['roles']
aggregationsExpand: ['roles'],
// Clean-up 'blocked_note' field content if blocked is false.
postprocessRecordEditor: (record: any) => {
if (record.blocked === false) {
delete record.blocked_note;
}
return record;
},
}
]
}
Expand Down
Loading

0 comments on commit a48d0a5

Please sign in to comment.