Skip to content

Commit

Permalink
circulation: display circulation notes automatically.
Browse files Browse the repository at this point in the history
When circulation operations are done, if item contains the corresponding
circulation note, this note will be displayed as a permanent toastr
message.

* refactoring item circulation collapsed informations design.
* adds postprocessRecord behavior to avoid empty notes array on item.

Co-authored-by : Renaud Michotte <renaud.michotte@gmail.com>
  • Loading branch information
zannkukai committed Jun 20, 2020
1 parent ba81cb7 commit 4a31735
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 44 deletions.
37 changes: 26 additions & 11 deletions projects/admin/src/app/circulation/checkin/checkin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ import { RecordService } from '@rero/ng-core';
import { ToastrService } from 'ngx-toastr';
import { map } from 'rxjs/operators';
import { User } from '../../class/user';
import { ItemsService } from '../../service/items.service';
import { PatronService } from '../../service/patron.service';
import { UserService } from '../../service/user.service';
import { ItemAction, ItemStatus } from '../items';
import { ItemsService } from '../../service/items.service';
import { Item, ItemAction, ItemNoteType, ItemStatus } from '../items';

@Component({
selector: 'admin-circulation-checkout',
Expand Down Expand Up @@ -56,7 +56,7 @@ export class CheckinComponent implements OnInit {
* @param _itemsService: Items Service
* @param _router: Router
* @param _translate: Translate Service
* @param toastService: Toastr Service
* @param _toastService: Toastr Service
* @param _patronService: Patron Service
*/
constructor(
Expand Down Expand Up @@ -122,6 +122,7 @@ export class CheckinComponent implements OnInit {
);
break;
case ItemAction.checkin:
this._displayCirculationNote(item, ItemNoteType.CHECKIN);
if (item.action_applied.checkin) {
this.getPatronInfo(item.action_applied.checkin.patron.barcode);
}
Expand Down Expand Up @@ -173,14 +174,8 @@ export class CheckinComponent implements OnInit {
this._recordService
.getRecords('patrons', `barcode:${barcode}`, 1, 1)
.pipe(
map((response: any) => {
if (response.hits.total === 0) {
return null;
}
return response.hits.hits[0].metadata;
})
)
.subscribe(
map((response: any) => (response.hits.total === 0) ? null : response.hits.hits[0].metadata)
).subscribe(
patron => {
if (
patron !== null &&
Expand Down Expand Up @@ -218,6 +213,26 @@ export class CheckinComponent implements OnInit {
);
}
}

/** display a circulation note about an item as a permanent toastr message
*
* @param item: the item
* @param noteType: the note type to display
*/
private _displayCirculationNote(item: Item, noteType: ItemNoteType): void {
const note = item.getNote(noteType);
if (note != null) {
this._toastService.warning(
note.content, null,
{
closeButton: true, // add a close button to the toastr message
disableTimeOut: true, // permanent toastr message (until click on 'close' button)
tapToDismiss: false // toastr message only close when click on the 'close' button.
}
);
}
}

hasFees(event: boolean) {
if (event) {
this._toastService.error(
Expand Down
79 changes: 55 additions & 24 deletions projects/admin/src/app/circulation/item/item.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,26 @@
 along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<div [ngClass]="{'callout callout-warning': item.actionDone === itemAction.checkin && (item.status === 'in_transit' || item.pending_loans || totalAmountOfFee > 0),
'text-secondary': item.status !== 'on_loan'}"
class="row p-2 mb-1 border rounded align-middle"
*ngIf="item">
<div *ngIf="item"
[ngClass]="{
'callout callout-warning': needCallout(item, 'warning') || (item.actionDone === itemAction.checkin && totalAmountOfFee > 0),
'text-secondary': item.status !== 'on_loan'
}"
class="row p-2 mb-1 border rounded align-middle">
<!-- BARCODE -->
<div class="col-lg-2">
<div class="col-lg-3">
<button *ngIf="item.loan || totalAmountOfFee || item.pending_loans || notifications$" type="button"
class="pl-0 pt-0 btn" (click)="isCollapsed = !isCollapsed" [attr.aria-expanded]="!isCollapsed"
aria-controls="collapse">
<i [ngClass]="{ 'fa-caret-down': !isCollapsed, 'fa-caret-right': isCollapsed }" class="fa" aria-hidden="true"></i>
</button>
<a [routerLink]="['/records','items','detail', item.pid]">{{ item.barcode }}</a>
<ng-container *ngIf="item.actionDone">
<ng-container *ngIf="(item.actionDone === itemAction.checkin && item.getNote('checkin_note')) ||
(item.actionDone === itemAction.checkout && item.getNote('checkout_note'))">
<i class="fa fa-sticky-note-o text-warning pt-1 float-right"></i>
</ng-container>
</ng-container>
</div>
<!-- TITLE -->
<div class="col-lg-4">
Expand Down Expand Up @@ -87,26 +95,49 @@
</ng-container>
</div>
<!-- COLLAPSED DETAILS -->
<div class="col-sm-6 mt-2">
<ul class="list-unstyled mb-0" id="collapse" [collapse]="isCollapsed" [isAnimated]="true">
<li *ngIf="item.loan && item.loan.pickup_location_pid">{{ 'Location' | translate }}:
{{ item.loan.pickup_location_pid | getRecord: 'locations' : 'field' : 'code' | async }} {{ item.location.name }}
</li>
<li *ngIf="item.loan && item.loan.extension_count">{{ 'Renewals' | translate }}: {{ item.loan.extension_count }}
</li>
<li *ngIf="totalAmountOfFee > 0">{{ 'Fees' | translate }}:
{{ totalAmountOfFee | currency: organisation.default_currency }}</li>
<div class="col-sm-12 mt-2" *ngIf="!isCollapsed">
<dl class="row">
<ng-container *ngIf="item.loan && item.loan.pickup_location_pid">
<dt class="col-sm-5 col-md-3 label-title" translate>Location</dt>
<dd class="col-sm-7 col-md-9">
{{ item.loan.pickup_location_pid | getRecord: 'locations' : 'field' : 'code' | async }} {{ item.location.name }}
</dd>
</ng-container>
<ng-container *ngIf="item.loan && item.loan.extension_count">
<dt class="col-sm-5 col-md-3 label-title" translate>Renewals</dt>
<dd class="col-sm-7 col-md-9">{{ item.loan.extension_count }}</dd>
</ng-container>
<ng-container *ngIf="totalAmountOfFee > 0">
<dt class="col-sm-5 col-md-3 label-title" translate>Fees</dt>
<dd class="col-sm-7 col-md-9">{{ totalAmountOfFee | currency: organisation.default_currency }}</dd>
</ng-container>
<ng-container *ngIf="notifications$ | async as notifications">
<li *ngIf="notifications.length > 0">{{ 'Notifications' | translate }}:
<ul class="list-unstyled pl-2 mb-0">
<li *ngFor="let notification of notifications">
{{ notification.metadata.process_date | dateTranslate :'shortDate' }}:
{{ notification.metadata.notification_type | translate}}</li>
</ul>
</li>
<ng-container *ngIf="notifications.length > 0">
<dt class="col-sm-5 col-md-3 label-title" translate>Notifications</dt>
<dd class="col-sm-7 col-md-9">
<ul class="list-unstyled pl-2 mb-0">
<li *ngFor="let notification of notifications">
{{ notification.metadata.process_date | dateTranslate :'shortDate' }}:
{{ notification.metadata.notification_type | translate }}
</li>
</ul>
</dd>
</ng-container>
</ng-container>
<li *ngIf="item.pending_loans">{{ 'Requests' | translate }}: {{ item.pending_loans.length }}</li>
<li *ngIf="item.pending_loans">{{ 'For' | translate }}: {{ item.pending_loans[0].patron.name }}</li>
</ul>
<ng-container *ngIf="item.pending_loans">
<dt class="col-sm-5 col-md-3 label-title" translate>Requests</dt>
<dd class="col-sm-7 col-md-9">{{ item.pending_loans.length }}</dd>
<dt class="col-sm-5 col-md-3 label-title" translate="">For</dt>
<dd class="col-sm-7 col-md-9">{{ item.pending_loans[0].patron.name }}</dd>
</ng-container>
<ng-container *ngIf="item.actionDone && item.actionDone === itemAction.checkin && item.getNote('checkin_note') as note">
<dt class="col-sm-5 col-md-3 label-title" translate>{{ note.type }}</dt>
<dd class="col-sm-7 col-md-9 text-justify">{{ note.content }}</dd>
</ng-container>
<ng-container *ngIf="item.actionDone && item.actionDone === itemAction.checkout && item.getNote('checkout_note') as note">
<dt class="col-sm-5 col-md-3 label-title" translate>{{ note.type }}</dt>
<dd class="col-sm-7 col-md-9 text-justify">{{ note.content }}</dd>
</ng-container>
</dl>
</div>
</div>
16 changes: 14 additions & 2 deletions projects/admin/src/app/circulation/item/item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { RecordService } from '@rero/ng-core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ItemsService } from '../../service/items.service';
import { OrganisationService } from '../../service/organisation.service';
import { Item, ItemAction, Loan, LoanState } from '../items';
import { PatronTransactionService } from '../patron-transaction.service';
Expand Down Expand Up @@ -55,12 +56,14 @@ export class ItemComponent implements OnInit {
* @param _recordService: Record Service
* @param _organisationService: Organisation Service
* @param _patronTransactionService: Patron transaction Service
* @param _itemService: Item Service
*/
constructor(
private _recordService: RecordService,
private _organisationService: OrganisationService,
private _patronTransactionService: PatronTransactionService
) { }
private _patronTransactionService: PatronTransactionService,
private _itemService: ItemsService
) { }

/**
* On init hook
Expand Down Expand Up @@ -116,4 +119,13 @@ export class ItemComponent implements OnInit {
get organisation() {
return this._organisationService.organisation;
}

/** Is a callout wrapper is required for this item.
*
* @param item: the item to analyse
* @param type: the callout type (error, warning, info, ...)
*/
needCallout(item: Item, type?: string): boolean {
return this._itemService.needCallout(item, type);
}
}
29 changes: 29 additions & 0 deletions projects/admin/src/app/circulation/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export enum ItemStatus {
MISSING = _('missing')
}

export enum ItemNoteType {
PUBLIC = _('public_note'),
STAFF = _('staff_note'),
CHECKIN = _('checkin_note'),
CHECKOUT = _('checkout_note')
}

export enum LoanState {
CREATED = _('CREATED'),
PENDING = _('PENDING'),
Expand Down Expand Up @@ -117,6 +124,12 @@ export class Loan {
return false;
}
}

export class ItemNote {
type: ItemNoteType;
content: string;
}

export class Item {
available: boolean;
barcode: string;
Expand All @@ -134,6 +147,7 @@ export class Item {
pending_loans: Loan[];
number_of_extensions: number;
location: any;
notes: ItemNote[];

constructor(obj?: any) {
Object.assign(this, obj);
Expand Down Expand Up @@ -176,4 +190,19 @@ export class Item {
public get hasRequests() {
return (this.pending_loans && this.pending_loans.length > 0);
}

/** Search on item notes a note corresponding to the note type
*
* @param type: the note type
* @return Return the corresponding note or null if not found
*/
public getNote(type: ItemNoteType): ItemNote | null {
if (this.notes == null) {
return null;
}
const filteredNotes = this.notes.filter(note => note.type === type);
return (filteredNotes)
? filteredNotes[0]
: null;
}
}
30 changes: 27 additions & 3 deletions projects/admin/src/app/circulation/patron/loan/loan.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import { TranslateService } from '@ngx-translate/core';
import { ToastrService } from 'ngx-toastr';
import { forkJoin } from 'rxjs';
import { User } from '../../../class/user';
import { PatronBlockedMessagePipe } from '../../../pipe/patron-blocked-message.pipe';
import { ItemsService } from '../../../service/items.service';
import { PatronService } from '../../../service/patron.service';
import { UserService } from '../../../service/user.service';
import { Item, ItemAction, ItemStatus } from '../../items';
import { ItemsService } from '../../../service/items.service';
import { PatronBlockedMessagePipe } from '../../../pipe/patron-blocked-message.pipe';
import { Item, ItemAction, ItemNoteType, ItemStatus } from '../../items';

@Component({
selector: 'admin-loan',
Expand Down Expand Up @@ -186,11 +186,13 @@ export class LoanComponent implements OnInit {
newItems.map(newItem => {
switch (newItem.actionDone) {
case ItemAction.checkin: {
this._displayCirculationNote(newItem, ItemNoteType.CHECKIN);
this.checkedOutItems = this.checkedOutItems.filter(currItem => currItem.pid !== newItem.pid);
this.checkedInItems.unshift(newItem);
break;
}
case ItemAction.checkout: {
this._displayCirculationNote(newItem, ItemNoteType.CHECKOUT);
this.checkedOutItems.unshift(newItem);
this.checkedInItems = this.checkedInItems.filter(currItem => currItem.pid !== newItem.pid);
break;
Expand Down Expand Up @@ -236,6 +238,25 @@ export class LoanComponent implements OnInit {
);
}

/** display a circulation note about an item as a permanent toastr message
*
* @param item: the item
* @param noteType: the note type to display
*/
private _displayCirculationNote(item: Item, noteType: ItemNoteType): void {
const note = item.getNote(noteType);
if (note != null) {
this._toastService.warning(
note.content, null,
{
closeButton: true, // add a close button to the toastr message
disableTimeOut: true, // permanent toastr message (until click on 'close' button)
tapToDismiss: false // toastr message only close when click on the 'close' button.
}
);
}
}

hasFees(event: boolean) {
if (event) {
this._toastService.error(
Expand All @@ -244,4 +265,7 @@ export class LoanComponent implements OnInit {
);
}
}


}

7 changes: 7 additions & 0 deletions projects/admin/src/app/routes/items-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export class ItemsRoute extends BaseRoute implements RouteInterface {
}
return data;
},
postprocessRecordEditor: (record: any) => {
// If we try to save an item with without any notes, then remove the empty array notes array from record
if (record.notes && record.notes.length === 0) {
delete record.notes;
}
return record;
},
formFieldMap: (field: FormlyFieldConfig, jsonSchema: JSONSchema7): FormlyFieldConfig => {
return this.populateLocationsByCurrentUserLibrary(
field, jsonSchema
Expand Down
4 changes: 3 additions & 1 deletion projects/admin/src/app/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ header {
content: " \f08e";
}


.toast-container .ngx-toastr {
width: 450px !important; // override width for all toastr message
}

json-schema-form {
input.ng-valid,
Expand Down
Loading

0 comments on commit 4a31735

Please sign in to comment.