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

items: issue sorting & late issue schedule task #3226

Merged
merged 1 commit into from
Feb 7, 2023
Merged
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
items: issue sorting & late issue schedule task
* Adds new `sort_date` for issue item. Used to sort issue regardless
  expected date.
* Adds extensions on `Item` resource to control/manage issue status and
  issue sort_date.
* Updates `late_issue_process` to include already created issue if
  expected_date has been manually changed by manager.
* Item/Holding code refactoring.
* Closes #2921.

Co-Authored-by: Renaud Michotte <renaud.michotte@gmail.com>
  • Loading branch information
zannkukai committed Feb 7, 2023
commit e9f15a70e74260daf829f74b19b90c4b4e1df84f
4 changes: 4 additions & 0 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2638,6 +2638,10 @@ def _(x):
fields=['issue.expected_date'], title='Issue expected date',
default_order='asc'
)
RECORDS_REST_SORT_OPTIONS['items']['issue_sort_date'] = dict(
fields=['issue.sort_date'], title='Issue chronology date',
default_order='asc'
)
RECORDS_REST_SORT_OPTIONS['items']['enumeration_chronology'] = dict(
fields=['-enumerationAndChronology'], title='Enumeration and Chronology',
default_order='desc'
Expand Down
11 changes: 11 additions & 0 deletions rero_ils/modules/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,17 @@ def count(cls, with_deleted=False):
"""Get record count."""
return cls._get_all(with_deleted=with_deleted).count()

def db_record(self):
"""Get record stored into DB for this record (aka original_record).

:returns: the record stored into database corresponding to the record
id. If the record ID doesn't yet exist, return None.
"""
try:
return self.__class__.get_record(self.id)
except NoResultFound:
pass

def delete(self, force=False, dbcommit=False, delindex=False):
"""Delete record and persistent identifier."""
can, _ = self.can_delete
Expand Down
44 changes: 17 additions & 27 deletions rero_ils/modules/holdings/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,17 +244,17 @@ def available(self):
@property
def max_number_of_claims(self):
"""Shortcut to return the max_number_of_claims."""
return self.get('patterns', {}).get('max_number_of_claims')
return self.get('patterns', {}).get('max_number_of_claims', 0)

@property
def days_before_first_claim(self):
"""Shortcut to return the days_before_first_claim."""
return self.get('patterns', {}).get('days_before_first_claim')
return self.get('patterns', {}).get('days_before_first_claim', 0)

@property
def days_before_next_claim(self):
"""Shortcut to return the days_before_next_claim."""
return self.get('patterns', {}).get('days_before_next_claim')
return self.get('patterns', {}).get('days_before_next_claim', 0)

@property
def vendor_pid(self):
Expand Down Expand Up @@ -344,40 +344,33 @@ def get_holdings_pid_by_document_pid_by_org(cls, document_pid, org_pid,

def get_items_filter_by_viewcode(self, viewcode):
"""Return items filter by view code."""
items = []
holdings_items = [
items = [
Item.get_record_by_pid(item_pid)
for item_pid in Item.get_items_pid_by_holding_pid(self.get('pid'))
]
if (viewcode != current_app.
config.get('RERO_ILS_SEARCH_GLOBAL_VIEW_CODE')):
org_pid = Organisation.get_record_by_viewcode(viewcode)['pid']
for item in holdings_items:
if item.organisation_pid == org_pid:
items.append(item)
return items
return holdings_items
return [item for item in items if item.organisation_pid == org_pid]
return items

@property
def get_items(self):
"""Return standard items and received issues for a holding record."""
for item_pid in Item.get_items_pid_by_holding_pid(self.pid):
item = Item.get_record_by_pid(item_pid)
if item:
if item := Item.get_record_by_pid(item_pid):
if not item.issue_status or \
item.issue_status == ItemIssueStatus.RECEIVED:
# inherit holdings first call#
# for issues with no 1st call#.
issue_call_number = item.issue_inherited_first_call_number
if issue_call_number:
item['call_number'] = issue_call_number
if call_number := item.issue_inherited_first_call_number:
item['call_number'] = call_number
yield item

def get_all_items(self):
"""Return all items a holding record."""
for item_pid in Item.get_items_pid_by_holding_pid(self.pid):
item = Item.get_record_by_pid(item_pid)
yield item
yield Item.get_record_by_pid(item_pid)

def get_links_to_me(self, get_pids=False):
"""Record links.
Expand Down Expand Up @@ -609,11 +602,7 @@ def _prepare_issue_data(issue, expected_date):
:param expected_date: The issue expected_date to prepare.
:return: The prepared issue data.
"""
issue_data = {
'issue': issue,
'expected_date': expected_date
}
return issue_data
return {'issue': issue, 'expected_date': expected_date}

def _prepare_issue_record(
self, item=None, issue_display=None, expected_date=None):
Expand All @@ -630,8 +619,7 @@ def _prepare_issue_record(
'status': 'on_shelf'
}
if item:
issue = item.pop('issue', None)
if issue:
if issue := item.pop('issue', None):
data['issue'].update(issue)
data.update(item)
# ensure that we have the right item fields such as location,
Expand All @@ -651,7 +639,7 @@ def _prepare_issue_record(

def receive_regular_issue(self, item=None, dbcommit=False, reindex=False):
"""Receive the next expected regular issue for the holdings record."""
# receive is allowed only on holdings of type serials and regular
# receive is allowed only on holdings of type serials with a regular
# frequency
if self.holdings_type != HoldingTypes.SERIAL \
or self.get('patterns', {}).get('frequency') == 'rdafr:1016':
Expand All @@ -660,8 +648,10 @@ def receive_regular_issue(self, item=None, dbcommit=False, reindex=False):
issue_display, expected_date = self._get_next_issue_display_text(
self.get('patterns'))
data = self._prepare_issue_record(
item=item, issue_display=issue_display,
expected_date=expected_date)
item=item,
issue_display=issue_display,
expected_date=expected_date
)
return Item.create(data=data, dbcommit=dbcommit, reindex=reindex)


Expand Down
25 changes: 12 additions & 13 deletions rero_ils/modules/holdings/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
import click
from flask.cli import with_appcontext

from ..documents.api import Document, DocumentsSearch
from ..holdings.api import Holding, create_holding
from ..item_types.api import ItemTypesSearch
from ..items.api import Item
from ..items.models import ItemIssueStatus
from ..items.tasks import process_late_issues
from ..locations.api import LocationsSearch
from ..organisations.api import Organisation
from ..utils import read_json_record
from rero_ils.modules.documents.api import Document, DocumentsSearch
from rero_ils.modules.holdings.api import Holding, create_holding
from rero_ils.modules.item_types.api import ItemTypesSearch
from rero_ils.modules.items.api import ItemIssue
from rero_ils.modules.items.models import ItemIssueStatus
from rero_ils.modules.items.tasks import process_late_issues
from rero_ils.modules.locations.api import LocationsSearch
from rero_ils.modules.organisations.api import Organisation
from rero_ils.modules.utils import read_json_record


def get_document_pid_by_rero_number(rero_control_number):
Expand Down Expand Up @@ -198,8 +198,7 @@ def create_patterns(infile, verbose, debug, lazy):
# create some late issues.
process_late_issues(dbcommit=True, reindex=True)
# make late issues ready for a claim
for item in Item.get_issues_by_status(issue_status=ItemIssueStatus.LATE):
holding = Holding.get_record_by_pid(item.holding_pid)
item['issue']['status_date'] = \
for issue in ItemIssue.get_issues_by_status(status=ItemIssueStatus.LATE):
issue['issue']['status_date'] = \
(datetime.now(timezone.utc) - timedelta(days=8)).isoformat()
item.update(item, dbcommit=True, reindex=True)
issue.update(issue, dbcommit=True, reindex=True)
72 changes: 72 additions & 0 deletions rero_ils/modules/holdings/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2019-2023 RERO
# Copyright (C) 2019-2023 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/>.

"""Utilities functions about `Holdings` resource."""

from datetime import datetime, timedelta, timezone

from flask import current_app

from rero_ils.modules.errors import RegularReceiveNotAllowed
from rero_ils.modules.holdings.api import Holding, HoldingsSearch
from rero_ils.modules.items.models import ItemIssueStatus


def get_late_serial_holdings():
"""Return serial holdings with late issues.

Holdings are considered late if :
* holding type is `serial`
* it's a `regular` serial holding (exclude irregular type)
* it's considered as alive (acq_status='currently_received')
* next expected date has passed (greater than current datetime).

:return: A `Holding` resource generator
"""
yesterday = datetime.now(timezone.utc) - timedelta(days=1)
yesterday = yesterday.strftime('%Y-%m-%d')
query = HoldingsSearch() \
.filter('term', holdings_type='serial') \
.filter('term', acquisition_status='currently_received') \
.filter('range', patterns__next_expected_date={'lte': yesterday}) \
.exclude('term', patterns__frequency='rdafr:1016') \
.source(False)
for hit in query.scan():
yield Holding.get_record(hit.meta.id)


def receive_next_late_expected_issues(dbcommit=False, reindex=False):
"""Receive the next late expected issue for all holdings.

:param reindex: reindex record by record.
:param dbcommit: commit record to database.
:return: the number of created issues.
"""
counter = 0
for holding in get_late_serial_holdings():
try:
issue = holding.receive_regular_issue(
dbcommit=dbcommit, reindex=reindex)
issue.issue_status = ItemIssueStatus.LATE
issue.update(issue, dbcommit=dbcommit, reindex=reindex)
counter += 1
except RegularReceiveNotAllowed as err:
pid = holding.pid
msg = f'Cannot receive next expected issue for Holding#{pid}'
current_app.logger.error(f'{msg}::{str(err)}')
return counter
Loading