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)}')
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
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
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
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
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)}')
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"])
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)
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
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)}')