Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loan: search date ranges #781

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions invenio_app_ils/circulation/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,11 @@ def checkout_loan(
)

return pid, loan


def loans_in_date_range(from_date, to_date):
"""Fetch the loans within the dates."""
search_cls = current_circulation.loan_search_cls
query = search_cls().get_loans_in_range(from_date, to_date)
result = query.execute()
return result
8 changes: 8 additions & 0 deletions invenio_app_ils/circulation/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ def get_all_overdue_loans(self):
])
return search

def get_loans_in_range(self, from_date=None, to_date=None):
"""Return loans in the given date range."""
search_cls = current_circulation.loan_search_cls
search = search_cls().query("bool", must=[
Q("range", start_date=dict(gte=from_date, lte=to_date)),
])
return search

class Meta:
"""Define permissions filter."""

Expand Down
64 changes: 61 additions & 3 deletions invenio_app_ils/circulation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@

from __future__ import absolute_import, print_function

from flask import Blueprint
import datetime

from flask import Blueprint, request
from invenio_circulation.links import loan_links_factory
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_PID_TYPE
from invenio_circulation.pidstore.pids import CIRCULATION_LOAN_FETCHER, \
CIRCULATION_LOAN_PID_TYPE
from invenio_pidstore import current_pidstore
from invenio_records_rest.utils import obj_or_import_string
from invenio_records_rest.views import pass_record
from invenio_rest import ContentNegotiatedMethodView

from invenio_app_ils.circulation.loaders import loan_checkout_loader, \
loan_request_loader
from invenio_app_ils.circulation.utils import circulation_overdue_loan_days
from invenio_app_ils.errors import OverdueLoansMailError
from invenio_app_ils.errors import InvalidParameterError, OverdueLoansMailError
from invenio_app_ils.permissions import need_permissions

from .api import checkout_loan, request_loan
Expand Down Expand Up @@ -79,6 +83,19 @@ def create_circulation_blueprint(app):
view_func=loan_mail_overdue,
methods=["POST"],
)

loan_in_date_range = LoanInDateRange.as_view(
LoanInDateRange.view_name.format(CIRCULATION_LOAN_PID_TYPE),
serializers=serializers,
default_media_type=default_media_type,
ctx=dict(links_factory=loan_links_factory),
)

blueprint.add_url_rule(
"{0}/in-date-range".format(options["item_route"]),
view_func=loan_in_date_range,
methods=["GET"],
)
return blueprint


Expand All @@ -94,6 +111,47 @@ def __init__(self, serializers, ctx, *args, **kwargs):
setattr(self, key, value)


class LoanInDateRange(IlsCirculationResource):
view_name = "loan_in_date_range"

def _validate_start_date_range(self):
"""Validate start date range parameters."""
def validate_date(param, date):
"""Validate a date."""
try:
return datetime.strptime(date, "%Y-%m-%d")
except ValueError:
msg = "Parameter '{}' is invalid: {}".format(param, date)
raise InvalidParameterError(description=msg)

from_date = request.args.get("from_date", None)
from_date_obj = None
to_date = request.args.get("to_date", None)
to_date_obj = None

if from_date:
from_date_obj = validate_date("from_date", from_date)
if to_date:
to_date_obj = validate_date("to_date", to_date)

if from_date_obj and to_date_obj and to_date_obj < from_date_obj:
msg = "Parameter 'to_date' cannot be before 'from_date'."
raise InvalidParameterError(description=msg)

return from_date, to_date

@need_permissions("circulation-loan-search")
def get(self, **kwargs):
"""Loan request post method."""

from_date, to_date = self._validate_start_date_range()
loans_in_date_range = loans_in_date_range(from_date, to_date)
return self.make_response(
pid_fetcher=current_pidstore.fetchers[CIRCULATION_LOAN_FETCHER],
search_result=loans_in_date_range,
)


class LoanRequestResource(IlsCirculationResource):
"""Loan request action resource."""

Expand Down
2 changes: 2 additions & 0 deletions invenio_app_ils/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def views_permissions_factory(action):
return backoffice_permission()
elif action == "circulation-loan-force-checkout":
return backoffice_permission()
elif action == "circulation-loan-search":
return backoffice_permission()
elif action == "circulation-overdue-loan-email":
return backoffice_permission()
elif action == "relations-create":
Expand Down
6 changes: 3 additions & 3 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.0",
"react-scroll": "^1.7.16",
"react-searchkit": "^0.16.0",
"react-searchkit": "^0.17.0",
"react-show-more": "^2.0.0",
"react-tagcloud": "^2.0.0",
"redux": "^4.0.4",
Expand Down
10 changes: 10 additions & 0 deletions ui/src/api/loans/loan.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { generatePath } from 'react-router-dom';
const apiPaths = {
checkout: '/circulation/loans/checkout',
emailOverdue: '/circulation/loans/:loanPid/email-overdue',
inDateRange: '/circulation/loans/in-date-range',
item: '/circulation/loans/:loanPid',
list: '/circulation/loans/',
request: '/circulation/loans/request',
Expand Down Expand Up @@ -266,10 +267,19 @@ const count = async query => {
return response;
};

const inDateRange = async (fromDate, toDate) => {
const response = await http.get(`${apiPaths.inDateRange}`, {
from_date: fromDate,
to_date: toDate,
});
return response.data;
};

export const loan = {
searchBaseURL: `${apiConfig.baseURL}${apiPaths.list}`,
assignItemToLoan: assignItemToLoan,
query: queryBuilder,
inDateRange: inDateRange,
list: list,
get: get,
count: count,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Card } from 'semantic-ui-react';
import { DatePicker } from '@components';

export class SearchDateRange extends Component {
render() {
const { fromDate, toDate, onDateChange } = this.props;
return (
<Card>
<Card.Content>
<Card.Header>Date</Card.Header>
<Card.Meta>
<span>*Loan start date</span>
</Card.Meta>
</Card.Content>
<Card.Content>
<DatePicker
maxDate={toDate}
defaultValue={fromDate}
placeholder="From"
handleDateChange={value => onDateChange({ fromDate: value })}
/>
</Card.Content>
<Card.Content>
<DatePicker
minDate={fromDate}
defaultValue={toDate}
placeholder="To"
handleDateChange={value => onDateChange({ toDate: value })}
/>
</Card.Content>
</Card>
);
}
}

SearchDateRange.propTypes = {
onDateChange: PropTypes.func.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SearchDateRange } from './SearchDateRange';
1 change: 1 addition & 0 deletions ui/src/components/SearchControls/components/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { SearchAggregationsCards } from './SearchAggregations';
export { SearchAggregationsMenu } from './SearchAggregations';
export { SearchDateRange } from './SearchDateRange';
export { SearchFooter } from './SearchFooter';
export { SearchPagination } from './SearchPagination';
export { SearchEmptyResults } from './SearchEmptyResults';
39 changes: 37 additions & 2 deletions ui/src/pages/backoffice/Loan/LoanSearch/LoanSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
ResultsLoader,
Error,
InvenioSearchApi,
onQueryChanged,
withState,
} from 'react-searchkit';
import { responseRejectInterceptor } from '@api/base';
import { getSearchConfig } from '@config';
Expand All @@ -23,7 +25,9 @@ import {
SearchAggregationsCards,
} from '@components/SearchControls';

export class LoanSearch extends Component {
import { SearchDateRange } from '@components/SearchControls/components';

class _LoanSearch extends Component {
searchApi = new InvenioSearchApi({
url: loanApi.searchBaseURL,
withCredentials: true,
Expand All @@ -33,6 +37,14 @@ export class LoanSearch extends Component {
});
searchConfig = getSearchConfig('loans');

constructor(props) {
super(props);
this.state = {
fromDate: '',
toDate: '',
};
}

renderSearchBar = (_, queryString, onInputChange, executeSearch) => {
const helperFields = [
{
Expand Down Expand Up @@ -72,12 +84,28 @@ export class LoanSearch extends Component {
return <LoanList hits={results} />;
};

onDateChange = updatedDate => {
this.setState(updatedDate, this.updateDateResults);
};

updateDateResults = () => {
const fromDate = this.state.fromDate || '*';
const toDate = this.state.toDate || '*';
onQueryChanged({
searchQuery: { queryString: `start_date:[${fromDate} TO ${toDate}]` },
});
};

render() {
return (
<>
<Header as="h2">Loans and requests</Header>

<ReactSearchKit searchApi={this.searchApi} history={history}>
<ReactSearchKit
searchApi={this.searchApi}
history={history}
eventListenerEnabled={true}
>
<Container fluid className="spaced">
<SearchBar renderElement={this.renderSearchBar} />
</Container>
Expand All @@ -87,6 +115,11 @@ export class LoanSearch extends Component {
<Grid.Column width={3} className={'search-aggregations'}>
<Header content={'Filter by'} />
<SearchAggregationsCards modelName={'loans'} />
<SearchDateRange
onDateChange={this.onDateChange}
fromDate={this.state.fromDate}
toDate={this.state.toDate}
/>
</Grid.Column>
<Grid.Column width={13}>
<SearchEmptyResults extras={this.renderEmptyResultsExtra} />
Expand All @@ -103,3 +136,5 @@ export class LoanSearch extends Component {
);
}
}

export const LoanSearch = withState(_LoanSearch);