Skip to content

Commit

Permalink
circulation: checkin operations when item/patron barcode are identical
Browse files Browse the repository at this point in the history
* Closes rero/rero-ils#2597.

Co-Authored-by: Bertrand Zuchuat <bertrand.zuchuat@rero.ch>
  • Loading branch information
Garfield-fr committed Dec 22, 2021
1 parent 4241991 commit bce1b3e
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--
RERO ILS UI
Copyright (C) 2021 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/>.
-->
<div class="modal show d-block" role="dialog" (window:keypress)="checkinItemOnReturnKey($event)">
<div class="modal-dialog modal-dialog-centered" role="checking">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" translate>Circulation action</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" data-dismiss="modal" (click)="closeModal()">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="font-weight-bold" translate>
One item and one patron were found.
</div>
</div>
<div class="modal-footer">
<button id="action-patron" type="button" class="btn btn-outline-primary" (click)="setAction('patron')">
<i class="fa fa-user mr-1"></i> {{ 'Patron account' | translate }}
</button>
<button id="action-item" type="button" class="btn btn-primary" (click)="setAction('item')">
<i class="fa fa-file-o mr-1"></i> {{ 'Checkin the item' | translate }}
</button>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* RERO ILS UI
* Copyright (C) 2021 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 { HttpClientTestingModule } from '@angular/common/http/testing';
import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
import { CirculationModule } from '../../circulation.module';
import { CheckinActionComponent } from './checkin-action.component';


describe('CheckinActionComponent', () => {
let component: CheckinActionComponent;
let fixture: ComponentFixture<CheckinActionComponent>

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
CirculationModule,
TranslateModule.forRoot(),
HttpClientTestingModule,
RouterTestingModule
],
declarations: [ CheckinActionComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(CheckinActionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should return the item action with a click on the button Check the item', () => {
const itemButton: DebugElement = fixture.debugElement.query(By.css('button[id=action-item]'));
itemButton.triggerEventHandler('click', null);
expect(component.action).toEqual('item');
});

it('should return the patron action with a click on the button Patron account', () => {
const itemButton: DebugElement = fixture.debugElement.query(By.css('button[id=action-patron]'));
itemButton.triggerEventHandler('click', null);
expect(component.action).toEqual('patron');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* RERO ILS UI
* Copyright (C) 2021 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 { Component } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';

@Component({
selector: 'admin-checkin-action',
templateUrl: './checkin-action.component.html'
})
export class CheckinActionComponent {

/** Checkin action */
public action: 'patron' | 'item';

/**
* Constructor
* @param _modalService - BsModalService
*/
constructor(private _modalService: BsModalService) { }

/**
* Set checkin action selected by user
* @param action - string
*/
setAction(action: 'patron' | 'item') {
this.action = action;
this.closeModal();
}

checkinItemOnReturnKey(event) {
if (event.keyCode === 13) {
this.action = 'item';
this.closeModal();
}
}

/** Close modal box */
closeModal() {
this._modalService.hide();
}
}
124 changes: 71 additions & 53 deletions projects/admin/src/app/circulation/checkin/checkin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TranslateService } from '@ngx-translate/core';
import { RecordService } from '@rero/ng-core';
import { Record, RecordService } from '@rero/ng-core';
import { ItemStatus, User, UserService } from '@rero/shared';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { ToastrService } from 'ngx-toastr';
import { forkJoin } from 'rxjs';
import { finalize, map } from 'rxjs/operators';
import { Item, ItemAction, ItemNoteType } from '../../classes/items';
import { ItemsService } from '../../service/items.service';
import { PatronService } from '../../service/patron.service';
import { CheckinActionComponent } from './checkin-action/checkin-action.component';

@Component({
selector: 'admin-circulation-checkout',
Expand All @@ -38,6 +41,7 @@ export class CheckinComponent implements OnInit {
currentLibraryPid: string;

private _loggedUser: User;

items = [];

/** Focus attribute of the search input */
Expand All @@ -54,6 +58,7 @@ export class CheckinComponent implements OnInit {
* @param _translate: TranslateService
* @param _toastService: ToastrService
* @param _patronService: PatronService
* @param _modalService: BsModalService
*/
constructor(
private _userService: UserService,
Expand All @@ -62,7 +67,8 @@ export class CheckinComponent implements OnInit {
private _router: Router,
private _translate: TranslateService,
private _toastService: ToastrService,
private _patronService: PatronService
private _patronService: PatronService,
private _modalService: BsModalService
) {}

ngOnInit() {
Expand Down Expand Up @@ -172,60 +178,71 @@ export class CheckinComponent implements OnInit {
* @param barcode: item or patron barcode
*/
getPatronOrItem(barcode: string) {
if (barcode) {
this._recordService
.getRecords('patrons', `${barcode}`, 1, 2, [], {simple: 1, roles: 'patron'})
.pipe(
map((response: any) => {
const total = this._recordService.totalHits(response.hits.total);
if (total === 0) {
return null;
}
if (total > 1) {
this._toastService.warning(
this._translate.instant('Found more than one patron.'),
this._translate.instant('Checkin')
);
}
return response.hits.hits[0].metadata;
})
).subscribe(
patron => {
if (
patron !== null &&
patron.organisation.pid !==
this._loggedUser.currentOrganisation
) {
this._toastService.warning(
this._translate.instant('Patron not found!'),
this._translate.instant('Checkin')
);
return;
}
if (patron === null) {
const newItem = this.items.find(item => item.barcode === barcode);
if (newItem) {
this._toastService.warning(
this._translate.instant('The item is already in the list.'),
this._translate.instant('Checkin')
);
} else {
this.checkin(barcode);
if (this.patronInfo === null) {
const loggerOrg = this._loggedUser.currentOrganisation;
const query = `patron.barcode:${barcode} AND organisation.pid:${loggerOrg}`;
const patronQuery = this._recordService
.getRecords('patrons', query, 1, 1, [])
.pipe(map((result: Record) => result.hits));
const itemQuery = this._recordService
.getRecords('items', `barcode:${barcode}`, 1, 1, [])
.pipe(map((result: Record) => result.hits));
forkJoin([patronQuery, itemQuery])
.subscribe(([patron, item]: any[]) => {
if (patron.total.value === 0 && item.total.value === 0) {
this._toastService.warning(
this._translate.instant('Patron not found!'),
this._translate.instant('Checkin')
);
}
if (patron.total.value > 1 && item.total.value === 0) {
this._toastService.warning(
this._translate.instant('Found more than one patron.'),
this._translate.instant('Checkin')
);
}
if (patron.total.value === 1 && item.total.value === 1) {
const modalRef: BsModalRef = this._modalService.show(CheckinActionComponent, {
ignoreBackdropClick: true,
keyboard: true
});
modalRef.onHidden.subscribe(() => {
switch (modalRef.content.action) {
case 'patron':
this._router.navigate(
['/circulation', 'patron', barcode, 'loan']
);
break;
case 'item':
this.checkin(barcode);
break;
default:
this._resetSearchInput();
break;
}
} else {
// TODO: which barcode we will take
this._router.navigate(
['/circulation', 'patron', patron.patron.barcode[0], 'loan']
);
});
} else {
if (item.total.value === 1) {
this.checkin(barcode);
}
this._resetSearchInput();
},
error =>
this._toastService.error(
error.message,
this._translate.instant('Checkin')
)
}
},
error => this._toastService.error(
error.message,
this._translate.instant('Checkin')
)
);
} else {
const newItem = this.items.find(item => item.barcode === barcode);
if (newItem) {
this._toastService.warning(
this._translate.instant('The item is already in the list.'),
this._translate.instant('Checkin')
);
this._resetSearchInput();
} else {
this.checkin(barcode);
}
}
}

Expand Down Expand Up @@ -269,6 +286,7 @@ export class CheckinComponent implements OnInit {
this._translate.instant('Checkin'),
{ enableHtml: true }
);
this._resetSearchInput();
})
).subscribe(
item => {
Expand Down
4 changes: 3 additions & 1 deletion projects/admin/src/app/circulation/circulation.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { PickupItemComponent } from './patron/pickup/pickup-item/pickup-item.com
import { PickupComponent } from './patron/pickup/pickup.component';
import { ProfileComponent } from './patron/profile/profile.component';
import { GetLoanCipoPipe } from './pipe/get-loan-cipo.pipe';
import { CheckinActionComponent } from './checkin/checkin-action/checkin-action.component';


@NgModule({
Expand Down Expand Up @@ -91,7 +92,8 @@ import { GetLoanCipoPipe } from './pipe/get-loan-cipo.pipe';
OverdueTransactionComponent,
HistoryLogComponent,
HistoryLogLibraryComponent,
GetLoanCipoPipe
GetLoanCipoPipe,
CheckinActionComponent
],
imports: [
BsDropdownModule.forRoot(),
Expand Down

0 comments on commit bce1b3e

Please sign in to comment.