def _circulation_for_document_resolver(document_pid): """Return circulation info for the given document.""" circulation = {} active_loans_count = 0 loanable_items_count = 0 items_count = 0 item_pids = circulation_items_retriever(document_pid) for pid in item_pids: items_count += 1 if not is_item_available(pid): active_loans_count += 1 if circulation_is_item_available(pid): loanable_items_count += 1 search = search_by_pid( document_pid=document_pid, filter_states=current_app.config.get( "CIRCULATION_STATES_LOAN_COMPLETED", []), ) pending_loans = search_by_pid(document_pid=document_pid, filter_states=['PENDING' ]).execute().hits.total circulation["number_of_past_loans"] = search.execute().hits.total circulation["number_of_items"] = items_count circulation["active_loans"] = active_loans_count circulation["loanable_items"] = loanable_items_count circulation["pending_loans"] = pending_loans circulation["overbooked"] = pending_loans > loanable_items_count return circulation
def delete(self, **kwargs): """Delete Document record.""" loan_search_res = search_by_pid( document_pid=self["pid"], filter_states=['PENDING'] + current_app.config['CIRCULATION_STATES_LOAN_ACTIVE'], ) if loan_search_res.count(): raise RecordHasReferencesError( record_type="Document", record_id=self["pid"], ref_type="Loan", ref_ids=sorted([res["pid"] for res in loan_search_res.scan()]), ) item_search = ItemSearch() item_search_res = item_search.search_by_document_pid( document_pid=self["pid"]) if item_search_res.count(): raise RecordHasReferencesError( record_type="Document", record_id=self["pid"], ref_type="Item", ref_ids=sorted([res["pid"] for res in item_search_res.scan()]), ) return super(Document, self).delete(**kwargs)
def test_loan_can_be_created_on_missing_item_by_force(client, json_headers, users, testdata): """Test that a patron can request a loan on a missing item by force checkout.""" for user in [users['admin'], users['librarian']]: login_user_via_session(client, email=User.query.get(user.id).email) url = url_for('invenio_app_ils_circulation.loan_create') item_exists = True if search_by_pid( item_pid=NEW_FORCED_LOAN["item_pid"]) \ .execute().hits.total > 0 else False if item_exists and patron_has_active_loan_on_item( patron_pid=NEW_FORCED_LOAN["patron_pid"], item_pid=NEW_FORCED_LOAN["item_pid"]): res = client.post(url, headers=json_headers, data=json.dumps(NEW_FORCED_LOAN)) assert res.status_code == 400 current_item_status = Item.get_record_by_pid( NEW_FORCED_LOAN["item_pid"])["status"] assert current_item_status == Item.get_record_by_pid( NEW_FORCED_LOAN["item_pid"])["status"] elif item_exists: res = client.post(url, headers=json_headers, data=json.dumps(NEW_FORCED_LOAN)) assert res.status_code == 202 loan = json.loads(res.data.decode('utf-8'))['metadata'] assert loan['state'] == 'ITEM_ON_LOAN'
def test_search_loans_by_pid_excluding_states(indexed_loans): """Test retrieve loan list belonging to an item excluding states.""" search_result = search_by_pid( item_pid=dict(type="itemid", value="item_multiple_pending_on_loan_7"), exclude_states=["ITEM_ON_LOAN"], ).execute() _assert_total(search_result.hits.total, 2)
def ensure_item_can_be_updated(self, record): """Raises an exception if the item's status cannot be updated.""" latest_version = record.revisions[-1] document_changed = False if latest_version: latest_version_document_pid = latest_version.get( "document_pid", None ) document_changed = latest_version_document_pid != record.get( "document_pid", None ) if document_changed: pid = record["pid"] item_pid = dict(value=pid, type=ITEM_PID_TYPE) if search_by_pid( item_pid=item_pid, filter_states=current_app.config[ "CIRCULATION_STATES_LOAN_ACTIVE" ] + current_app.config["CIRCULATION_STATES_LOAN_COMPLETED"] + current_app.config["CIRCULATION_STATES_LOAN_CANCELLED"], ).count(): raise ItemHasPastLoansError( "Not possible to update the document field if " "the item already has past or active loans." )
def test_search_loans_by_pid(indexed_loans): """Test retrieve loan list belonging to an item.""" item_pid = dict(type="itemid", value="item_pending_1") loans = list(search_by_pid(item_pid=item_pid).scan()) assert len(loans) == 1 loan = Loan.get_record_by_pid(loans[0]["pid"]) assert loan["item_pid"] == item_pid
def get_past_loans_by_doc_pid(self, document_pid): """Return any past loans for the given document.""" return search_by_pid( document_pid=document_pid, filter_states=current_app.config.get( "CIRCULATION_STATES_LOAN_COMPLETED", []), )
def get_overdue_loans_by_doc_pid(self, document_pid): """Return any overdue loans for the given document.""" return search_by_pid( document_pid=document_pid, filter_states=current_app.config.get( "CIRCULATION_STATES_LOAN_ACTIVE", []), ).filter('range', end_date=dict(lt='now/d'))
def get_active_loan_by_item_pid(item_pid): """Return any active loans for the given item.""" return search_by_pid( item_pid=item_pid, filter_states=current_app.config.get("CIRCULATION_STATES_LOAN_ACTIVE", []), )
def get_active_loans_by_doc_pid(self, document_pid): """Return any active loans for the given document.""" return search_by_pid( document_pid=document_pid, filter_states=current_app.config.get( "CIRCULATION_STATES_LOAN_ACTIVE", []), )
def get_loan_next_available_date(document_pid): """Return active loans on document sorted by the earliest end date.""" return search_by_pid( document_pid=document_pid, filter_states=current_app.config.get("CIRCULATION_STATES_LOAN_ACTIVE", []), sort_by_field="end_date", )
def test_search_loans_by_pid_filtering_states(indexed_loans): """Test retrieve loan list belonging to an item filtering states.""" search = search_by_pid( item_pid=dict(type="itemid", value="item_multiple_pending_on_loan_7"), filter_states=["PENDING", "ITEM_ON_LOAN"], ) search_result = search.execute() _assert_total(search_result.hits.total, 3)
def get_number_of_loans(self): """Get number of document loans.""" search = search_by_pid(document_pid=self.pid, exclude_states=[ 'CANCELLED', 'ITEM_RETURNED', ]) return search.source().count()
def get_number_of_loans(self): """Get number of loans.""" search = search_by_pid(item_pid=self.pid, exclude_states=[ 'CANCELLED', 'ITEM_RETURNED', ]) results = search.source().count() return results
def get_loan_pid_with_item_on_loan(cls, item_pid): """Returns loan pid for checked out item.""" search = search_by_pid( item_pid=item_pid, filter_states=['ITEM_ON_LOAN']) results = search.source(['pid']).scan() try: return next(results).pid except StopIteration: return None
def get_number_of_loans(self): """Get number of document loans.""" from ..loans.api import LoanState search = search_by_pid(document_pid=self.pid, exclude_states=[ LoanState.CANCELLED, LoanState.ITEM_RETURNED, ]) return search.source().count()
def index_loans_after_item_indexed(item_pid): """Index loan to refresh item reference.""" loan_search = search_by_pid(item_pid=item_pid) loan_ids = [] for loan in loan_search.scan(): record = Loan.get_record_by_pid(loan[Loan.pid_field]) if record: loan_ids.append(record.id) RecordIndexer().bulk_index(loan_ids)
def item_has_active_loan_or_request(self): """Return True if active loan or a request found for item.""" states = ['PENDING'] + \ current_app.config['CIRCULATION_STATES_LOAN_ACTIVE'] search = search_by_pid( item_pid=self.pid, filter_states=states, ) search_result = search.execute() return search_result.hits.total
def get_loan_pid_with_item_in_transit(cls, item_pid): """Returns loan pi for in_transit item.""" search = search_by_pid( item_pid=item_pid, filter_states=[ "ITEM_IN_TRANSIT_FOR_PICKUP", "ITEM_IN_TRANSIT_TO_HOUSE"]) results = search.source(['pid']).scan() try: return next(results).pid except StopIteration: return None
def delete(self, **kwargs): """Delete Item record.""" loan_search_res = search_by_pid( item_pid=self["pid"], filter_states=current_app.config['CIRCULATION_STATES_LOAN_ACTIVE']) if loan_search_res.count(): raise RecordHasReferencesError( record_type='Item', record_id=self["pid"], ref_type='Loan', ref_ids=sorted([res["pid"] for res in loan_search_res.scan()])) return super(Item, self).delete(**kwargs)
def get_requests(self): """Return any pending, item_on_transit, item_at_desk loans.""" search = search_by_pid( item_pid=self.pid, filter_states=[ 'PENDING', 'ITEM_AT_DESK', 'ITEM_IN_TRANSIT_FOR_PICKUP' ]).params(preserve_order=True)\ .source(['pid'])\ .sort({'transaction_date': {'order': 'asc'}}) for result in search.scan(): yield Loan.get_record_by_pid(result.pid)
def search_active_loans_for_item(item_pid): """Return count and all active loans for an item.""" item_pid_object = item_pid_to_object(item_pid) states = ['PENDING'] + \ current_app.config['CIRCULATION_STATES_LOAN_ACTIVE'] search = search_by_pid(item_pid=item_pid_object, filter_states=states, sort_by_field='_created', sort_order='desc') loans_count = search.count() try: return loans_count, search.scan() except StopIteration: return loans_count
def delete(self, **kwargs): """Delete Item record.""" item_pid = dict(type=ITEM_PID_TYPE, value=self["pid"]) loan_search_res = search_by_pid( item_pid=item_pid, filter_states=current_app.config["CIRCULATION_STATES_LOAN_ACTIVE"], ) if loan_search_res.count(): raise RecordHasReferencesError( record_type="Item", record_id=self["pid"], ref_type="Loan", ref_ids=sorted([res["pid"] for res in loan_search_res.scan()]), ) return super().delete(**kwargs)
def get_requests(self, sort_by=None): """Return sorted pending, item_on_transit, item_at_desk loans. default sort is transaction_date. """ search = search_by_pid(item_pid=self.pid, filter_states=[ 'PENDING', 'ITEM_AT_DESK', 'ITEM_IN_TRANSIT_FOR_PICKUP' ]).params(preserve_order=True).source(['pid']) order_by = 'asc' sort_by = sort_by or 'transaction_date' if sort_by.startswith('-'): sort_by = sort_by[1:] order_by = 'desc' search = search.sort({sort_by: {'order': order_by}}) for result in search.scan(): yield Loan.get_record_by_pid(result.pid)
def delete(self, **kwargs): """Delete Document record.""" loan_search_res = search_by_pid( document_pid=self["pid"], filter_states=["PENDING"] + current_app.config["CIRCULATION_STATES_LOAN_ACTIVE"], ) if loan_search_res.count(): raise RecordHasReferencesError( record_type="Document", record_id=self["pid"], ref_type="Loan", ref_ids=sorted([res["pid"] for res in loan_search_res.scan()]), ) item_search = current_app_ils.item_search_cls() item_search_res = item_search.search_by_document_pid( document_pid=self["pid"]) if item_search_res.count(): raise RecordHasReferencesError( record_type="Document", record_id=self["pid"], ref_type="Item", ref_ids=sorted([res["pid"] for res in item_search_res.scan()]), ) req_search = current_app_ils.document_request_search_cls() req_search_res = req_search.search_by_document_pid( document_pid=self["pid"]) if req_search_res.count(): raise RecordHasReferencesError( record_type="Document", record_id=self["pid"], ref_type="DocumentRequest", ref_ids=sorted([res["pid"] for res in req_search_res.scan()]), ) return super().delete(**kwargs)
def test_search_loans_by_pid_excluding_states(indexed_loans): """Test retrieve loan list belonging to an item excluding states.""" search_result = search_by_pid(item_pid="item_multiple_pending_on_loan_7", exclude_states=["ITEM_ON_LOAN"]).execute() assert search_result.hits.total == 2
def get_overdue_loans_by_doc_pid(document_pid): """Return any overdue loans for the given document.""" states = current_app.config["CIRCULATION_STATES_LOAN_ACTIVE"] return search_by_pid(document_pid=document_pid, filter_states=states).filter( "range", end_date=dict(lt="now/d"))
def test_search_loans_by_pid(indexed_loans): """Test retrieve loan list belonging to an item.""" loans = list(search_by_pid(item_pid="item_pending_1").scan()) assert len(loans) == 1 loan = Loan.get_record_by_pid(loans[0][Loan.pid_field]) assert loan.get("item_pid") == "item_pending_1"
def test_search_loans_by_pid_filtering_states(indexed_loans): """Test retrieve loan list belonging to an item filtering states.""" search = search_by_pid(item_pid="item_multiple_pending_on_loan_7", filter_states=["PENDING", "ITEM_ON_LOAN"]) search_result = search.execute() assert search_result.hits.total == 3
def get_links_to_me(self, get_pids=False): """Record links. :param get_pids: if True list of linked pids if False count of linked records """ links = {} from ..holdings.api import HoldingsSearch from ..items.api import ItemsSearch from ..loans.models import LoanState hold_query = HoldingsSearch().filter('term', document__pid=self.pid) item_query = ItemsSearch().filter('term', document__pid=self.pid) loan_query = search_by_pid( document_pid=self.pid, exclude_states=[LoanState.CANCELLED, LoanState.ITEM_RETURNED]) acq_order_lines_query = AcqOrderLinesSearch() \ .filter('term', document__pid=self.pid) relation_types = { 'partOf': 'partOf.document.pid', 'supplement': 'supplement.pid', 'supplementTo': 'supplementTo.pid', 'otherEdition': 'otherEdition.pid', 'otherPhysicalFormat': 'otherPhysicalFormat.pid', 'issuedWith': 'issuedWith.pid', 'precededBy': 'precededBy.pid', 'succeededBy': 'succeededBy.pid', 'relatedTo': 'relatedTo.pid', 'hasReproduction': 'hasReproduction.pid', 'reproductionOf': 'reproductionOf.pid' } if get_pids: holdings = sorted_pids(hold_query) items = sorted_pids(item_query) loans = sorted_pids(loan_query) acq_order_lines = sorted_pids(acq_order_lines_query) documents = {} for relation, relation_es in relation_types.items(): doc_query = DocumentsSearch() \ .filter({'term': {relation_es: self.pid}}) pids = sorted_pids(doc_query) if pids: documents[relation] = pids else: holdings = hold_query.count() items = item_query.count() loans = loan_query.count() acq_order_lines = acq_order_lines_query.count() documents = 0 for relation, relation_es in relation_types.items(): doc_query = DocumentsSearch() \ .filter({'term': {relation_es: self.pid}}) documents += doc_query.count() if holdings: links['holdings'] = holdings if items: links['items'] = items if loans: links['loans'] = loans if acq_order_lines: links['acq_order_lines'] = acq_order_lines if documents: links['documents'] = documents return links
def test_search_loans_by_pid(indexed_loans): """Test retrieve loan list belonging to an item.""" loans = list(search_by_pid(item_pid="item_pending_1").scan()) assert len(loans) == 1 loan = Loan.get_record_by_pid(loans[0]["pid"]) assert loan.get("item_pid") == "item_pending_1"