def upgrade():
    """Update loans records."""
    query = current_circulation.loan_search_cls() \
        .filter('term', state=LoanState.ITEM_AT_DESK) \
        .filter('bool', must_not=[Q('exists', field='request_expire_date')]) \
        .source('pid')
    loan_pids = [hit.pid for hit in query.scan()]
    ids = []
    for pid in loan_pids:
        loan = Loan.get_record_by_pid(pid)
        trans_date = ciso8601.parse_datetime(loan.transaction_date)
        expire_date = trans_date + timedelta(days=10)
        expire_date = expire_date.replace(hour=23,
                                          minute=59,
                                          second=00,
                                          microsecond=000,
                                          tzinfo=None)
        expire_date = pytz.timezone('Europe/Zurich').localize(expire_date)
        loan['request_expire_date'] = expire_date.isoformat()
        loan['request_start_date'] = datetime.now().isoformat()
        loan.update(loan, dbcommit=True, reindex=False)
        LOGGER.info(f'  * Updated loan#{loan.pid}')
        ids.append(loan.id)
    if len(ids):
        LOGGER.info(f'Indexing {len(ids)} records ....')
        indexer = LoansIndexer()
        indexer.bulk_index(ids)
        count = indexer.process_bulk_queue()
        LOGGER.info(f'{count} records indexed.')
    LOGGER.info(f'TOTAL :: {len(ids)}')
Beispiel #2
0
    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
        """
        if not self.pid:
            return
        links = {}
        exclude_states = [
            LoanState.CANCELLED, LoanState.ITEM_RETURNED, LoanState.CREATED
        ]
        loan_query = current_circulation.loan_search_cls()\
            .filter('term', patron_pid=self.pid)\
            .exclude('terms', state=exclude_states)
        template_query = TemplatesSearch()\
            .filter('term', creator__pid=self.pid)
        if get_pids:
            loans = sorted_pids(loan_query)
            transactions = get_transactions_pids_for_patron(self.pid,
                                                            status='open')
            templates = sorted_pids(template_query)
        else:
            loans = loan_query.count()
            transactions = get_transactions_count_for_patron(self.pid,
                                                             status='open')
            templates = template_query.count()
        if loans:
            links['loans'] = loans
        if transactions:
            links['transactions'] = transactions
        if templates:
            links['templates'] = templates
        return links
Beispiel #3
0
 def get_number_of_loans(self):
     """Get number of loans."""
     from ..loans.api import LoanState
     exclude_states = [LoanState.CANCELLED, LoanState.ITEM_RETURNED]
     results = current_circulation.loan_search_cls()\
         .filter('term', patron_pid=self.pid)\
         .exclude('terms', state=exclude_states)\
         .source().count()
     return results
Beispiel #4
0
def get_due_soon_loans():
    """Return all due_soon loans."""
    due_soon_loans = []
    results = current_circulation.loan_search_cls()\
        .filter('term', state=LoanState.ITEM_ON_LOAN)\
        .params(preserve_order=True)\
        .sort({'_created': {'order': 'asc'}})\
        .source(['pid']).scan()
    for record in results:
        loan = Loan.get_record_by_pid(record.pid)
        if is_due_soon_loan(loan):
            due_soon_loans.append(loan)
    return due_soon_loans
Beispiel #5
0
def get_last_transaction_loc_for_item(item_pid):
    """Return last transaction location for an item."""
    results = current_circulation.loan_search_cls()\
        .filter('term', item_pid=item_pid)\
        .params(preserve_order=True)\
        .exclude('terms', state=[
            LoanState.PENDING, LoanState.CREATED])\
        .sort({'_created': {'order': 'desc'}})\
        .source(['pid']).scan()
    try:
        loan_pid = next(results).pid
        return Loan.get_record_by_pid(loan_pid).get('transaction_location_pid')
    except StopIteration:
        return None
Beispiel #6
0
def clean_loans(user_email, given_date):
    """Clean loans and doc requests created by given user on given date."""
    patron = User.query.filter_by(email=user_email).one()
    patron_pid = patron.get_id()

    patron_document_requests = (
        current_app_ils.document_request_search_cls().filter(
            "bool",
            filter=[
                Q("term", patron_pid=patron_pid),
                Q("term", _created=given_date),
            ],
        ).scan())

    for hit in patron_document_requests:
        document_request = (
            current_app_ils.document_request_record_cls.get_record_by_pid(
                hit.pid))
        document_request.delete(force=True)
        db.session.commit()
        current_app_ils.document_indexer.delete(document_request)

    patron_loans = (current_circulation.loan_search_cls().filter(
        "bool",
        filter=[
            Q("term", patron_pid=patron_pid),
            Q("term", _created=given_date),
        ],
    ).scan())

    for hit in patron_loans:
        loan = current_circulation.loan_record_cls.get_record_by_pid(hit.pid)
        loan.delete(force=True)
        db.session.commit()
        current_circulation.loan_indexer().delete(loan)
        if "item_pid" in loan:
            item = current_app_ils.item_record_cls.get_record_by_pid(
                loan["item_pid"]["value"])
            loan_index = current_circulation.loan_search_cls.Meta.index
            wait_es_refresh(loan_index)
            current_app_ils.item_indexer.index(item)

    current_search.flush_and_refresh(index="*")

    click.secho(
        "Loans and document requests of user with pid '{0}' have been deleted."
        .format(patron_pid),
        fg="blue",
    )
def downgrade():
    """Downgrade Loan records removing `checkout_location_pid` field."""
    query = current_circulation.loan_search_cls() \
        .filter('exists', field='checkout_location_pid') \
        .source('pid')
    loans_hits = [hit for hit in query.scan()]
    ids = []
    for hit in loans_hits:
        loan = Loan.get_record_by_pid(hit.pid)
        del loan['checkout_location_pid']
        loan.update(loan, dbcommit=True, reindex=False)
        LOGGER.info(f'  * Downgrade loan#{loan.pid}')
        ids.append(loan.id)
    _indexing_records(ids)
    LOGGER.info(f'TOTAL :: {len(ids)}')
Beispiel #8
0
 def ensure_item_can_be_updated(self, record):
     """Raises an exception if the item's status cannot be updated."""
     latest_version = record.revisions[-1]
     if latest_version:
         status = latest_version.get("status", None)
     else:
         status = None
     pid = record["pid"]
     if status == "CAN_CIRCULATE":
         item_pid = dict(value=pid, type=ITEM_PID_TYPE)
         search = current_circulation.loan_search_cls()
         active_loan = (
             search.get_active_loan_by_item_pid(item_pid).execute().hits)
         total = active_loan.total if lt_es7 else active_loan.total.value
         if total > 0:
             raise ItemHasActiveLoanError(active_loan[0]["pid"])
Beispiel #9
0
def get_non_anonymized_loans(patron_pid=None, org_pid=None):
    """Search all loans for non anonymized loans.

    :param patron_pid: optional parameter to filter by patron_pid.
    :param org_pid: optional parameter to filter by organisation.
    :return: loans.
    """
    search = current_circulation.loan_search_cls()\
        .filter('term', to_anonymize=False)\
        .filter('terms', state=[LoanState.CANCELLED, LoanState.ITEM_RETURNED])\
        .source(['pid'])
    if patron_pid:
        search = search.filter('term', patron_pid=patron_pid)
    if org_pid:
        search = search.filter('term', organisation__pid=org_pid)
    for record in search.scan():
        yield Loan.get_record_by_pid(record.pid)
Beispiel #10
0
def get_overdue_loan_pids(patron_pid=None):
    """Return all overdue loan pids optionally filtered for a patron pid.

    :param patron_pid: the patron pid. If none, return all overdue loans.
    :return a generator of loan pid
    """
    end_date = datetime.now()
    end_date = end_date.strftime('%Y-%m-%d')
    query = current_circulation.loan_search_cls() \
        .filter('term', state=LoanState.ITEM_ON_LOAN) \
        .filter('range', end_date={'lte': end_date})
    if patron_pid:
        query = query.filter('term', patron_pid=patron_pid)
    results = query\
        .params(preserve_order=True) \
        .sort({'_created': {'order': 'asc'}}) \
        .source(['pid']).scan()
    for hit in results:
        yield hit.pid
Beispiel #11
0
    def circulation_resolver(document_pid):
        """Return circulation info for the given Document."""
        # loans
        loan_search = current_circulation.loan_search_cls()
        past_loans_count = loan_search.get_past_loans_by_doc_pid(
            document_pid).count()
        active_loans_count = loan_search.get_active_loans_by_doc_pid(
            document_pid).count()
        pending_loans_count = loan_search.get_pending_loans_by_doc_pid(
            document_pid).count()
        overdue_loans_count = loan_search.get_overdue_loans_by_doc_pid(
            document_pid).count()

        # items
        ItemSearch = current_app_ils.item_search_cls()
        items_count = ItemSearch.search_by_document_pid(document_pid).count()
        unavailable_items_count = ItemSearch. \
            get_unavailable_items_by_document_pid(document_pid).count()
        has_items_for_loan = items_count - \
            active_loans_count - unavailable_items_count
        has_items_for_reference_only_count = ItemSearch \
            .get_for_reference_only_by_document_pid(document_pid).count()

        circulation = {
            "active_loans": active_loans_count,
            "can_circulate_items_count": items_count - unavailable_items_count,
            "has_items_for_loan": has_items_for_loan,
            "overbooked": pending_loans_count > has_items_for_loan,
            "overdue_loans": overdue_loans_count,
            "past_loans_count": past_loans_count,
            "pending_loans": pending_loans_count,
            "has_items_on_site": has_items_for_reference_only_count
        }

        if circulation["overbooked"] or circulation["active_loans"] >= \
                circulation["has_items_for_loan"]:
            next_available_loans = loan_search.get_loan_next_available_date(
                document_pid).execute()
            if next_available_loans:
                circulation["next_available_date"] = \
                    next_available_loans.hits[0].end_date
        return circulation
def upgrade():
    """Upgrade ON_LOAN records.

    For all ON_LOAN records, we will add a new `checkout_location_pid` field
    used to calculate fees based on checkout library.
    """
    query = current_circulation.loan_search_cls() \
        .filter('term', state=LoanState.ITEM_ON_LOAN) \
        .filter('bool', must_not=[
            Q('exists', field='checkout_location_pid')
        ]) \
        .source(['pid', 'transaction_location_pid'])
    loans_hits = [hit for hit in query.scan()]
    ids = []
    for hit in loans_hits:
        loan = Loan.get_record_by_pid(hit.pid)
        loan['checkout_location_pid'] = hit.transaction_location_pid
        loan.update(loan, dbcommit=True, reindex=False)
        LOGGER.info(f'  * Upgrade loan#{loan.pid}')
        ids.append(loan.id)
    _indexing_records(ids)
    LOGGER.info(f'TOTAL :: {len(ids)}')