diff --git a/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.html b/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.html index aa5c668e8..ddaaaf91f 100644 --- a/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.html +++ b/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.html @@ -1,6 +1,6 @@ +
+ + + + + + +
diff --git a/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.ts b/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.ts index 59c3c1253..3a5de46d4 100644 --- a/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.ts +++ b/projects/admin/src/app/circulation/patron/pending/pending-item/pending-item.component.ts @@ -1,6 +1,6 @@ /* * RERO ILS UI - * Copyright (C) 2020 RERO + * Copyright (C) 2020-2023 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 @@ -14,8 +14,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { LoanService } from '@app/admin/service/loan.service'; +import { TranslateService } from '@ngx-translate/core'; import { RecordService } from '@rero/ng-core'; +import { UserService } from '@rero/shared'; +import { ToastrService } from 'ngx-toastr'; import { forkJoin } from 'rxjs'; import { ItemsService } from '../../../../service/items.service'; @@ -33,16 +37,26 @@ export class PendingItemComponent implements OnInit { document = undefined; /** detail is collapsed */ isCollapsed = true; + /** Informs parent component to remove request when it is cancelled */ + @Output() cancelRequestEvent = new EventEmitter(); // CONSTRUCTOR & HOOKS ====================================================== /** * Constructor * @param _recordService - RecordService * @param _itemService - ItemService + * @param _translateService - TranslateService + * @param _loanService - LoanService + * @param _userService - UserService + * @param _toastrService - ToastrService */ constructor( private _recordService: RecordService, - private _itemService: ItemsService + private _itemService: ItemsService, + private _translateService: TranslateService, + private _loanService: LoanService, + private _userService: UserService, + private _toastrService: ToastrService, ) { } /** OnInit hook */ @@ -58,4 +72,31 @@ export class PendingItemComponent implements OnInit { ); } } + + /** + * Can cancel a loan request + * @returns true if it is possible to cancel a loan + */ + canCancelRequest(): boolean { + return this._loanService.canCancelRequest(this.loan); + } + + /** Show a confirmation dialog box for cancel request. */ + showCancelRequestDialog(): void { + this._loanService.cancelRequestDialog().subscribe((confirm: boolean) => { + if (confirm) { + this._loanService.cancelLoan( + this.loan.metadata.item.pid, + this.loan.metadata.pid, + this._userService.user.currentLibrary + ).subscribe(() => { + this._toastrService.warning( + this._translateService.instant('The pending request has been cancelled.'), + this._translateService.instant('Request') + ); + this.cancelRequestEvent.emit(this.loan.id); + }); + } + }); + } } diff --git a/projects/admin/src/app/circulation/patron/pending/pending.component.html b/projects/admin/src/app/circulation/patron/pending/pending.component.html index b03542e98..5cc0daa55 100644 --- a/projects/admin/src/app/circulation/patron/pending/pending.component.html +++ b/projects/admin/src/app/circulation/patron/pending/pending.component.html @@ -1,6 +1,6 @@
Item
-
Title
+
Title
Request date
Expected availability
+
 
- +
diff --git a/projects/admin/src/app/circulation/patron/pending/pending.component.ts b/projects/admin/src/app/circulation/patron/pending/pending.component.ts index 9fdd33fea..aeb92b411 100644 --- a/projects/admin/src/app/circulation/patron/pending/pending.component.ts +++ b/projects/admin/src/app/circulation/patron/pending/pending.component.ts @@ -1,6 +1,6 @@ /* * RERO ILS UI - * Copyright (C) 2020 RERO + * Copyright (C) 2020-2023 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 @@ -17,6 +17,7 @@ import { Component, OnInit } from '@angular/core'; import { PatronService } from '../../../service/patron.service'; +import { CirculationService } from '../../services/circulation.service'; @Component({ selector: 'admin-pending', @@ -31,7 +32,10 @@ export class PendingComponent implements OnInit { * Constructor * @param _patronService - PatronService */ - constructor(private _patronService: PatronService) {} + constructor( + private _patronService: PatronService, + private _circulationService: CirculationService + ) {} /** * Init @@ -46,4 +50,16 @@ export class PendingComponent implements OnInit { } }); } + + /** + * Remove the canceled request on the loans list. + * @param loanId, the canceled loan id + */ + cancelRequest(loanId: any): void { + // Remove loan in list + const index = this.loans.findIndex((element: any) => element.id == loanId); + this.loans.splice(index, 1); + // Update count on tab + this._circulationService.circulationInformations.statistics['pending'] -= 1; + } } diff --git a/projects/admin/src/app/record/detail-view/item-detail-view/item-transaction/item-transaction.component.ts b/projects/admin/src/app/record/detail-view/item-detail-view/item-transaction/item-transaction.component.ts index 3794d9026..fa505c1f2 100644 --- a/projects/admin/src/app/record/detail-view/item-detail-view/item-transaction/item-transaction.component.ts +++ b/projects/admin/src/app/record/detail-view/item-detail-view/item-transaction/item-transaction.component.ts @@ -1,6 +1,6 @@ /* * RERO ILS UI - * Copyright (C) 2019 RERO + * Copyright (C) 2019-2023 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 @@ -16,11 +16,10 @@ */ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { ItemsService } from '@app/admin/service/items.service'; +import { LoanService } from '@app/admin/service/loan.service'; import { TranslateService } from '@ngx-translate/core'; -import { DialogService } from '@rero/ng-core'; import { UserService } from '@rero/shared'; -import { LoanState } from '@app/admin/classes/loans'; -import { ItemsService } from '@app/admin/service/items.service'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -33,7 +32,7 @@ export class ItemTransactionComponent implements OnInit, OnDestroy { // COMPONENTS ATTRIBUTES =============================================================== /** Loan Record */ @Input() transaction: any; - /** Ressource type */ + /** Resource type */ @Input() type: string; /** Flag for cell background */ @Input() background: boolean; @@ -54,7 +53,7 @@ export class ItemTransactionComponent implements OnInit, OnDestroy { /** Pickup locations observable reference */ private _pickupLocations$: any; /** Authorized Transaction Type to load pickup locations */ - private _autorizedTypeToLoadPickupLocations = [ + private _authorizedTypeToLoadPickupLocations = [ 'loan_request' ]; @@ -77,20 +76,20 @@ export class ItemTransactionComponent implements OnInit, OnDestroy { * constructor * @param _userService - User service * @param _translateService - TranslateService - * @param _dialogService: DialogService - * @param _itemService: ItemsService + * @param _itemService - ItemsService + * @param _loanService - LoanService */ constructor( private _userService: UserService, private _translateService: TranslateService, - private _dialogService: DialogService, - private _itemService: ItemsService + private _itemService: ItemsService, + private _loanService: LoanService ) {} /** OnInit hook */ ngOnInit() { this._currentUser = this._userService.user; - if (this._autorizedTypeToLoadPickupLocations.includes(this.type)) { + if (this._authorizedTypeToLoadPickupLocations.includes(this.type)) { this._pickupLocations$ = this.getPickupLocations().subscribe((pickups) => { this.pickupLocations = pickups; }); @@ -99,43 +98,32 @@ export class ItemTransactionComponent implements OnInit, OnDestroy { /** OnDestroy hook */ ngOnDestroy() { - if (this._autorizedTypeToLoadPickupLocations.includes(this.type)) { + if (this._authorizedTypeToLoadPickupLocations.includes(this.type)) { this._pickupLocations$.unsubscribe(); } } // COMPONENT FUNCTIONS ================================================================== - /** Check if request can be cancelled. */ + /** + * Check if request can be cancelled. + * @returns true if it is possible to cancel a loan + */ canCancelRequest(): boolean { - // No permission API in backend - // Allowed when loan state is PENDING or ITEM_IN_TRANSIT_FOR_PICKUP according to actions.md - const itemStatus = [LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP, LoanState.ITEM_AT_DESK]; - return itemStatus.some((element) => element === this.transaction.metadata.state); - + return this._loanService.canCancelRequest(this.transaction); } - /** Check if request pickup location can be changed. */ + /** + * Check if request pickup location can be changed. + * @returns true if it is possible to update pickup location. + */ canUpdateRequestPickupLocation(): boolean { - // No permission API in backend - // Allowed when loan state is PENDING or ITEM_IN_TRANSIT_FOR_PICKUP according to actions.md - const itemStatus = [LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP]; - return itemStatus.some((element) => element === this.transaction.metadata.state); + return this._loanService.canUpdateRequestPickupLocation(this.transaction); } /** Show a confirmation dialog box for cancel request. */ showCancelRequestDialog(): void { - const config = { - ignoreBackdropClick: true, - initialState: { - title: this._translateService.instant('Cancel request'), - body: this._translateService.instant('Do you really want to delete this request?'), - confirmButton: true, - cancelTitleButton: this._translateService.instant('No'), - confirmTitleButton: this._translateService.instant('Yes') - } - }; - this._dialogService.show(config).subscribe((confirm: boolean) => { + this._loanService.cancelRequestDialog().subscribe((confirm: boolean) => { if (confirm) { this.emitCancelRequest(); } diff --git a/projects/admin/src/app/record/detail-view/item-detail-view/item-transactions/item-transactions.component.ts b/projects/admin/src/app/record/detail-view/item-detail-view/item-transactions/item-transactions.component.ts index 52ade80e3..2fcc5cff3 100644 --- a/projects/admin/src/app/record/detail-view/item-detail-view/item-transactions/item-transactions.component.ts +++ b/projects/admin/src/app/record/detail-view/item-detail-view/item-transactions/item-transactions.component.ts @@ -96,7 +96,7 @@ export class ItemTransactionsComponent implements OnInit { .subscribe((itemData: any) => { const status = this._translateService.instant(itemData.status); this._toastrService.warning( - this._translateService.instant('The item is {{ status }}', { status }), + this._translateService.instant('The pending request has been cancelled.'), this._translateService.instant('Request') ); this.cancelRequestEvent.emit(); diff --git a/projects/admin/src/app/service/loan.service.spec.ts b/projects/admin/src/app/service/loan.service.spec.ts index ce70f5ddb..847fcd91a 100644 --- a/projects/admin/src/app/service/loan.service.spec.ts +++ b/projects/admin/src/app/service/loan.service.spec.ts @@ -19,13 +19,15 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { LoanService } from './loan.service'; +import { RecordModule } from '@rero/ng-core'; describe('LoanService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [ HttpClientTestingModule, - TranslateModule.forRoot() + TranslateModule.forRoot(), + RecordModule ] })); diff --git a/projects/admin/src/app/service/loan.service.ts b/projects/admin/src/app/service/loan.service.ts index 0fa0f59ac..bc807aa61 100644 --- a/projects/admin/src/app/service/loan.service.ts +++ b/projects/admin/src/app/service/loan.service.ts @@ -1,6 +1,6 @@ /* * RERO ILS UI - * Copyright (C) 2019 RERO + * Copyright (C) 2019-2023 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 @@ -17,13 +17,14 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { RecordService } from '@rero/ng-core'; +import { DialogService, RecordService } from '@rero/ng-core'; import { Record } from '@rero/ng-core/lib/record/record'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { CircPolicy } from '../classes/circ-policy'; import { LoanState } from '../classes/loans'; import { UserService } from '@rero/shared'; +import { TranslateService } from '@ngx-translate/core'; @Injectable({ providedIn: 'root' @@ -49,11 +50,14 @@ export class LoanService { * @param _recordService - RecordService * @param _http - HttpClient * @param _userService - UserService + * @param _translateService - TranslateService */ constructor( private _recordService: RecordService, private _http: HttpClient, - private _userService: UserService + private _userService: UserService, + private _translateService: TranslateService, + private _dialogService: DialogService ) { } // SERVICES FUNCTIONS ======================================================= @@ -81,6 +85,17 @@ export class LoanService { ); } + /** + * Can cancel a request + * @param loan - Loan record + * @returns true if it is possible to cancel a loan + */ + canCancelRequest(loan: any): boolean { + // TODO: To increase the complexity, it is better to implement a backend api + return [LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP, LoanState.ITEM_AT_DESK] + .some((element) => element === loan.metadata.state) + } + /** * Cancel a loan related to an item * @param itemPid - item pid related to the loan to cancel @@ -105,6 +120,16 @@ export class LoanService { ); } + /** + * Check if request pickup location can be changed. + * @returns true if it is possible to update pickup location. + */ + canUpdateRequestPickupLocation(transaction: any): boolean { + // TODO: To increase the complexity, it is better to implement a backend api + return [LoanState.PENDING, LoanState.ITEM_IN_TRANSIT_FOR_PICKUP] + .some((element) => element === transaction.metadata.state) + } + /** * Update the pickup location of a loan * @param loanPid - loan pid to update @@ -129,13 +154,30 @@ export class LoanService { return this._http.get(apiUrl); } + /** + * Cancel request dialog. + * @returns Observable (true if the user confirms the cancellation) + */ + cancelRequestDialog(): Observable { + const config = { + ignoreBackdropClick: true, + initialState: { + title: this._translateService.instant('Cancel request'), + body: this._translateService.instant('Do you really want to cancel the request?'), + confirmButton: true, + cancelTitleButton: this._translateService.instant('No'), + confirmTitleButton: this._translateService.instant('Yes') + } + }; + return this._dialogService.show(config); + } // PRIVATES SERVICE FUNCTIONS =============================================== /** * Search about loans related to an item * @param itemPid - the item pid to search * @param statuses - a list of loan states to filter result. - * @returns Observable with loans corresponding to search critieria + * @returns Observable with loans corresponding to search criteria */ private loans$(itemPid: string, statuses?: Array): Observable { let query = `item_pid.value:${itemPid}`;