Skip to content

Commit

Permalink
request: basic ill request management.
Browse files Browse the repository at this point in the history
Adds basic functionalities to manage the ill requests into the
professional interface :
  * adds menu entry to list ILL requests (with faceting).
  * adds brief/detail view for ILL requests.
  * allows ILL request creation by staff members.
  * allows ILL request management (edit) by staff members.

This commit also implements an autocomplete typeahead widget on patron
resource.

Authored-by: Renaud Michotte <renaud.michotte@gmail.com>
  • Loading branch information
zannkukai committed Nov 25, 2020
1 parent 2c089b9 commit 384ba94
Show file tree
Hide file tree
Showing 24 changed files with 679 additions and 36 deletions.
32 changes: 20 additions & 12 deletions projects/admin/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DocumentsTypeahead } from './class/documents-typeahead';
import { ItemsTypeahead } from './class/items-typeahead';
import { MefOrganisationTypeahead } from './class/mef-organisation-typeahead';
import { MefPersonTypeahead } from './class/mef-person-typeahead';
import { MefTypeahead } from './class/mef-typeahead';
import { DocumentsTypeahead } from './class/typeahead/documents-typeahead';
import { ItemsTypeahead } from './class/typeahead/items-typeahead';
import { MefOrganisationTypeahead } from './class/typeahead/mef-organisation-typeahead';
import { MefPersonTypeahead } from './class/typeahead/mef-person-typeahead';
import { MefTypeahead } from './class/typeahead/mef-typeahead';
import { PatronsTypeahead } from './class/typeahead/patrons-typeahead';
import { TabOrderDirective } from './directives/tab-order.directive';
import { ErrorPageComponent } from './error/error-page/error-page.component';
import { NoCacheHeaderInterceptor } from './interceptor/no-cache-header.interceptor';
Expand Down Expand Up @@ -89,7 +90,6 @@ import {
} from './record/detail-view/contribution-detail-view/corporate-bodies-detail-view/corporate-bodies-detail-view.component';
import { PersonDetailViewComponent } from './record/detail-view/contribution-detail-view/person-detail-view/person-detail-view.component';
import { DocumentDetailViewComponent } from './record/detail-view/document-detail-view/document-detail-view.component';
import { HoldingDetailComponent } from './record/detail-view/document-detail-view/holding-detail/holding-detail.component';
import {
DefaultHoldingItemComponent
} from './record/detail-view/document-detail-view/holding/default-holding-item/default-holding-item.component';
Expand Down Expand Up @@ -130,9 +130,12 @@ import { AppConfigService } from './service/app-config.service';
import { AppInitService } from './service/app-init.service';
import { typeaheadToken } from './service/typeahead-factory.service';
import { UiRemoteTypeaheadService } from './service/ui-remote-typeahead.service';
import { CustomShortcutHelpComponent } from './widgets/custom-shortcut-help/custom-shortcut-help.component';
import { FrontpageBoardComponent } from './widgets/frontpage/frontpage-board/frontpage-board.component';
import { FrontpageComponent } from './widgets/frontpage/frontpage.component';
import { HoldingDetailComponent } from './record/detail-view/document-detail-view/holding-detail/holding-detail.component';
import { CustomShortcutHelpComponent } from './widgets/custom-shortcut-help/custom-shortcut-help.component';
import { IllRequestsBriefViewComponent } from './record/brief-view/ill-requests-brief-view/ill-requests-brief-view.component';
import { IllRequestDetailViewComponent } from './record/detail-view/ill-request-detail-view/ill-request-detail-view.component';

/** Init application factory */
export function appInitFactory(appInitService: AppInitService) {
Expand Down Expand Up @@ -214,10 +217,12 @@ export function appInitFactory(appInitService: AppInitService) {
HoldingItemInCollectionComponent,
DocumentRecordSearchComponent,
HoldingDetailComponent,
CustomShortcutHelpComponent,
ContributionDetailViewComponent,
PersonDetailViewComponent,
CorporateBodiesDetailViewComponent
CorporateBodiesDetailViewComponent,
IllRequestsBriefViewComponent,
IllRequestDetailViewComponent,
CustomShortcutHelpComponent
],
imports: [
AppRoutingModule,
Expand Down Expand Up @@ -262,6 +267,7 @@ export function appInitFactory(appInitService: AppInitService) {
{ provide: typeaheadToken, useExisting: ItemsTypeahead, multi: true },
{ provide: typeaheadToken, useExisting: MefOrganisationTypeahead, multi: true },
{ provide: typeaheadToken, useExisting: MefPersonTypeahead, multi: true },
{ provide: typeaheadToken, useExisting: PatronsTypeahead, multi: true },
{
provide: CoreConfigService,
useClass: AppConfigService
Expand All @@ -275,20 +281,22 @@ export function appInitFactory(appInitService: AppInitService) {
MefTypeahead,
DocumentsTypeahead,
ItemsTypeahead,
PatronsTypeahead,
MainTitlePipe,
MefPersonTypeahead,
MefOrganisationTypeahead,
TruncateTextPipe,
// TODO: needed for production build, remove this after it is fixed in the
// @ngneat/hotkeys library
{
provide: HotkeysService,
useClass: HotkeysService
},
MefPersonTypeahead,
MefOrganisationTypeahead,
TruncateTextPipe,
MainTitlePipe
],
entryComponents: [
IllRequestsBriefViewComponent,
IllRequestDetailViewComponent,
CircPoliciesBriefViewComponent,
CirculationPolicyComponent,
DocumentEditorComponent,
Expand Down
33 changes: 33 additions & 0 deletions projects/admin/src/app/class/ill-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* RERO ILS UI
* Copyright (C) 2020 RERO
* Copyright (C) 2020 UCLouvain
*
* 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/>.
*/

/* tslint:disable */
// required as json properties is not lowerCamelCase

import { marker } from '@biesbjerg/ngx-translate-extract-marker';

export function _(str) {
return marker(str);
}

export enum ILLRequestStatus {
PENDING = _('pending'),
VALIDATED = _('validated'),
DENIED = _('denied'),
CLOSED = _('closed'),
}
118 changes: 118 additions & 0 deletions projects/admin/src/app/class/typeahead/patrons-typeahead.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* RERO ILS UI
* Copyright (C) 2020 RERO
* Copyright (C) 2020 UCLouvain
*
* 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 { Inject, Injectable } from '@angular/core';
import { ApiService, RecordService, SuggestionMetadata } from '@rero/ng-core';
import { of, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PatronService } from '../../service/patron.service';
import { ITypeahead } from './ITypeahead-interface';

/**
* Escape string using regular expression.
*/
function escapeRegExp(data) {
return data.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

@Injectable()
export class PatronsTypeahead implements ITypeahead {

/**
* Constructor
* @param _apiService - ApiService
* @param _recordService - RecordService
* @param _patronService - PatronService
*/
constructor(
private _apiService: ApiService,
private _recordService: RecordService,
private _patronService: PatronService
) { }

/** Get name of typeahead */
getName() {
return 'patrons';
}

/**
* Convert the input value (i.e. $ref url) into a template html code.
* @param options - remote typeahead options
* @param value - formControl value i.e. $ref value
* @returns Observable of string - html template representation of the value.
*/
getValueAsHTML(options: any, value: string): Observable<string> {
const url = value.split('/');
const pid = url.pop();

return this._recordService
.getRecord(options.type, pid, 1)
.pipe(
map((data: any) =>
`<span class="bg-light p-2"><strong>${this._patronService.getFormattedName(data.metadata)}</strong></span>`
)
);
}

/**
* Get the suggestions list given a search query.
* @param options - remote typeahead options
* @param query - search query to retrieve the suggestions list
* @param numberOfSuggestions - the max number of suggestion to return
* @returns - an observable of the list of suggestions.
*/
getSuggestions(options: any, query: string, numberOfSuggestions: number): Observable<Array<SuggestionMetadata>> {
if (!query) {
return of([]);
}
return this._recordService
.getRecords(
'patrons',
`${query}`,
1,
numberOfSuggestions
).pipe(
map((results: any) => {
const patrons = [];
if (results) {
results.hits.hits.map((hit: any) => {
patrons.push(this._getPatronsRef(hit.metadata, query));
});
}
return patrons;
})
);
}

/**
* Returns label and $ref.
* @param metadata - the meta data.
* @param query - search query term.
* @return Metadata - the label, $ref.
*/
private _getPatronsRef(metadata: any, query: string): SuggestionMetadata {
let label = this._patronService.getFormattedName(metadata);
if (metadata.hasOwnProperty('birth_date')) {
label += `<small class="ml-2 font-weight-bold">[${metadata.birth_date}]</small>`;
}
return {
label,
value: this._apiService.getRefEndpoint('patrons', metadata.pid)
};
}
}
14 changes: 8 additions & 6 deletions projects/admin/src/app/menu/menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,12 @@ export class MenuComponent implements OnInit {
if (element.routerLink.indexOf('/libraries/detail') > -1) {
element.routerLink = this.myLibraryRouterLink();
}
// update items list query params
if (element.routerLink.indexOf('/records/items') > -1) {
element.queryParams = this.myItemListQueryParams();
// update library query params
// TODO : refactoring when all UI menus will be rewritten
if (element.routerLink.indexOf('/records/items') > -1
|| element.routerLink.indexOf('/records/ill_requests') > -1
|| element.routerLink.indexOf('/records/collections') > -1) {
element.queryParams = this.myLibraryQueryParams();
}
}
);
Expand All @@ -197,11 +200,10 @@ export class MenuComponent implements OnInit {
}

/**
* Query param to filter items list by current library
*
* Query param to filter resource by current logges user library
* @return library pid as a dictionary
*/
private myItemListQueryParams() {
private myLibraryQueryParams() {
return {library: this._userService.user.currentLibrary};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!--
RERO ILS UI
 Copyright (C) 2020 RERO
Copyright (C) 2020 UCLouvain

 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/>.
-->
<ng-container *ngIf="record && requester">
<span class="badge badge-{{ badgeColor }} float-right px-2 py-1" translate>{{ record.metadata.status }}</span>
<h5 class="mb-0">
<a [routerLink]="[detailUrl.link]">
{{ record.metadata.document.title }}
</a>
</h5>
<div class="small" *ngIf="record.metadata.document.authors as authors">{{ authors }}</div>
<dl class="mt-2 row">
<dt class="col-sm-6 col-md-3 label-title" translate>Requested by</dt>
<dd class="col-sm-6 col-md-9 mb-0">
<a *ngIf="requester && requester.patron && requester.patron.barcode" [routerLink]="['/circulation', 'patron', requester.patron.barcode]">
<span id="patron-last-name">{{ requester.last_name }}</span>
<span id="patron-first-name" *ngIf="requester.first_name as firstName">, {{ firstName }}</span>
</a>
</dd>
<dt class="col-sm-6 col-md-3 label-title" translate>Request date</dt>
<dd class="col-sm-6 col-md-9 mb-0">{{ record.created | dateTranslate:'medium' }}</dd>
</dl>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* RERO ILS UI
* Copyright (C) 2020 RERO
* Copyright (C) 2020 UCLouvain
*
* 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, Input, OnInit } from '@angular/core';
import { RecordService, ResultItem } from '@rero/ng-core';
import { User } from '@rero/shared';
import { ILLRequestStatus } from '../../../class/ill-request';

@Component({
selector: 'admin-ill-requests-brief-view',
templateUrl: './ill-requests-brief-view.component.html'
})
export class IllRequestsBriefViewComponent implements ResultItem, OnInit {

// COMPONENT ATTRIBUTES =======================================================
/** Record */
@Input() record: any;
/** Type of record */
@Input() type: string;
/** Detail Url */
@Input() detailUrl: { link: string, external: boolean };

/** the requester of the ILL request */
requester: User = null;


// GETTER FUNCTIONS ==========================================================
/** get the bootsrap color to apply on the request status badge */
get badgeColor(): string {
if (this.record) {
switch (this.record.metadata.status) {
case ILLRequestStatus.PENDING: return 'warning';
case ILLRequestStatus.VALIDATED: return 'success';
case ILLRequestStatus.DENIED: return 'danger';
default: return 'secondary';
}
}
return 'secondary';
}

// CONSTRUCTOR & HOOKS =======================================================
/**
* Constructor
* @param _recordService - RecordService
*/
constructor(
private _recordService: RecordService
) {}

/** Init hook */
ngOnInit() {
if (this.record) {
this._recordService.getRecord('patrons', this.record.metadata.patron.pid).subscribe(
(patron) => this.requester = new User(patron.metadata)
);
}
}

}
Loading

0 comments on commit 384ba94

Please sign in to comment.