Skip to content

Commit

Permalink
acquisition: create library order
Browse files Browse the repository at this point in the history
* Adds order resource for acquisition.
* Adds permissions for order resource.
* Adds order line resource for acquisition.
* Adds library and order_status facets for orders.
* Adds sort to order lines.
* Creates tests.
* Creates fixtures for tests.
* Fixes fuzzy translations for vendors.

Co-Authored-by: Lauren-D <laurent.dubois@itld-solutions.be>
lauren-d authored and iGor milhit committed Jan 23, 2020
1 parent 2c71a6e commit c89e90b
Showing 34 changed files with 3,412 additions and 273 deletions.
109 changes: 106 additions & 3 deletions rero_ils/config.py
Original file line number Diff line number Diff line change
@@ -47,6 +47,8 @@
from .modules.acq_accounts.api import AcqAccount
from .modules.acq_accounts.permissions import can_create_acq_account_factory, \
can_list_acq_account_factory, can_read_update_delete_acq_account_factory
from .modules.acq_order_lines.api import AcqOrderLine, AcqOrderLinesIndexer
from .modules.acq_orders.api import AcqOrder
from .modules.budgets.api import Budget
from .modules.budgets.permissions import can_create_budgets_factory, \
can_list_budgets_factory, can_update_delete_budgets_factory
@@ -80,9 +82,10 @@
from .modules.persons.api import Person
from .modules.vendors.api import Vendor
from .permissions import can_access_organisation_patrons_factory, \
can_access_organisation_records_factory, \
can_access_organisation_records_factory, can_create_acquisition_factory, \
can_create_organisation_records_factory, \
can_delete_organisation_records_factory, \
can_delete_organisation_records_factory, can_list_acquisition_factory, \
can_read_update_delete_acquisition_factory, \
can_update_organisation_records_factory, \
librarian_delete_permission_factory, librarian_permission_factory, \
librarian_update_permission_factory
@@ -876,7 +879,76 @@ def _(x):
create_permission_factory_imp=can_create_budgets_factory,
update_permission_factory_imp=can_update_delete_budgets_factory,
delete_permission_factory_imp=can_update_delete_budgets_factory,
)
),
acor=dict(
pid_type='acor',
pid_minter='acq_order_id',
pid_fetcher='acq_order_id',
search_class=RecordsSearch,
search_index='acq_orders',
search_type=None,
indexer_class=IlsRecordIndexer,
record_serializers={
'application/json': (
'rero_ils.modules.serializers:json_v1_response'
)
},
search_serializers={
'application/json': (
'rero_ils.modules.serializers:json_v1_search'
),
'application/rero+json': (
'rero_ils.modules.acq_orders.serializers:json_acq_order_search'
),
},
record_loaders={
'application/json': lambda: AcqOrder(request.get_json()),
},
record_class='rero_ils.modules.acq_orders.api:AcqOrder',
list_route='/acq_orders/',
item_route='/acq_orders/<pid(acor, record_class="rero_ils.modules.acq_orders.api:AcqOrder"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=can_access_organisation_records_factory,
list_permission_factory_imp=can_list_acquisition_factory,
create_permission_factory_imp=can_create_acquisition_factory,
update_permission_factory_imp=can_read_update_delete_acquisition_factory,
delete_permission_factory_imp=can_read_update_delete_acquisition_factory,
),
acol=dict(
pid_type='acol',
pid_minter='acq_order_line_id',
pid_fetcher='acq_order_line_id',
search_class=RecordsSearch,
search_index='acq_order_lines',
search_type=None,
indexer_class=AcqOrderLinesIndexer,
record_serializers={
'application/json': (
'rero_ils.modules.serializers:json_v1_response'
)
},
search_serializers={
'application/json': (
'rero_ils.modules.serializers:json_v1_search'
)
},
record_loaders={
'application/json': lambda: AcqOrderLine(request.get_json()),
},
record_class='rero_ils.modules.acq_order_lines.api:AcqOrderLine',
list_route='/acq_order_lines/',
item_route='/acq_order_lines/<pid(acol, record_class="rero_ils.modules.acq_order_lines.api:AcqOrderLine"):pid_value>',
default_media_type='application/json',
max_result_window=10000,
search_factory_imp='rero_ils.query:organisation_search_factory',
read_permission_factory_imp=can_access_organisation_records_factory,
list_permission_factory_imp=can_list_acquisition_factory,
create_permission_factory_imp=can_create_acquisition_factory,
update_permission_factory_imp=can_read_update_delete_acquisition_factory,
delete_permission_factory_imp=can_read_update_delete_acquisition_factory,
),
)

SEARCH_UI_SEARCH_INDEX = 'documents'
@@ -978,6 +1050,28 @@ def _(x):
_('budget'): terms_filter('budget')
},
),
acq_orders=dict(
aggs=dict(
library=dict(
terms=dict(
field='library.pid',
size=RERO_ILS_AGGREGATION_SIZE.get(
'acq_orders', RERO_ILS_DEFAULT_AGGREGATION_SIZE)
)
),
status=dict(
terms=dict(
field='order_status',
size=RERO_ILS_AGGREGATION_SIZE.get(
'acq_orders', RERO_ILS_DEFAULT_AGGREGATION_SIZE)
)
)
),
filters={
_('library'): terms_filter('library.pid'),
_('status'): terms_filter('order_status')
},
),
persons=dict(
aggs=dict(
sources=dict(
@@ -1058,6 +1152,13 @@ def _(x):
)
)

RECORDS_REST_SORT_OPTIONS['acq_order_lines'] = dict(
pid=dict(
fields=['_id'], title='Order line PID',
default_order='asc'
)
)


# Detailed View Configuration
# ===========================
@@ -1131,6 +1232,8 @@ def _(x):
'vndr': '/vendors/vendor-v0.0.1.json',
'acac': '/acq_accounts/acq_account-v0.0.1.json',
'budg': '/budgets/budget-v0.0.1.json',
'acor': '/acq_orders/acq_order-v0.0.1.json',
'acol': '/acq_order_lines/acq_order_line-v0.0.1.json',
}

# Login Configuration
18 changes: 18 additions & 0 deletions rero_ils/modules/acq_order_lines/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2019 RERO
#
# 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/>.

"""Acquisition Order Line Records."""
164 changes: 164 additions & 0 deletions rero_ils/modules/acq_order_lines/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2019 RERO
#
# 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/>.

"""API for manipulating Acquisition Order Line."""

from functools import partial

from flask import current_app

from .models import AcqOrderLineIdentifier
from ..api import IlsRecord, IlsRecordIndexer, IlsRecordsSearch
from ..fetchers import id_fetcher
from ..minters import id_minter
from ..providers import Provider

# provider
AcqOrderLineProvider = type(
'AcqOrderLineProvider',
(Provider,),
dict(identifier=AcqOrderLineIdentifier, pid_type='acol')
)
# minter
acq_order_line_id_minter = partial(id_minter, provider=AcqOrderLineProvider)
# fetcher
acq_order_line_id_fetcher = partial(id_fetcher, provider=AcqOrderLineProvider)


class AcqOrderLinesIndexer(IlsRecordIndexer):
"""Indexing Acquisition Order Line in Elasticsearch."""

def index(self, record):
"""Index an Acquisition Order Line and update total amount of order."""
return_value = super(AcqOrderLinesIndexer, self).index(record)
self._update_order_total_amount(record)

return return_value

def delete(self, record):
"""Delete a Acquisition Order Line and update total amount of order."""
return_value = super(AcqOrderLinesIndexer, self).delete(record)
self._update_order_total_amount(record)

return return_value

def _update_order_total_amount(self, record):
"""Update total amount of the order."""
from ..acq_orders.api import AcqOrder

order_pid = record.replace_refs()['acq_order']['pid']
order = AcqOrder.get_record_by_pid(order_pid)
order['total_amount'] = order.get_order_total_amount()
order.update(order, dbcommit=True, reindex=True)


class AcqOrderLinesSearch(IlsRecordsSearch):
"""Acquisition Order Line Search."""

class Meta:
"""Search only on Acquisition Order Line index."""

index = 'acq_order_lines'


class AcqOrderLine(IlsRecord):
"""Acquisition Order Line class."""

minter = acq_order_line_id_minter
fetcher = acq_order_line_id_fetcher
provider = AcqOrderLineProvider

@classmethod
def create(cls, data, id_=None, delete_pid=False,
dbcommit=True, reindex=True, **kwargs):
"""Create Acquisition Order Line record."""
cls._acq_order_line_build_org_ref(data)
cls._build_total_amount_for_order_line(data)
record = super(AcqOrderLine, cls).create(
data, id_, delete_pid, dbcommit, reindex, **kwargs)
return record

def update(self, data, dbcommit=True, reindex=True):
"""Update Acquisition Order Line record."""
self._build_total_amount_for_order_line(data)
super(AcqOrderLine, self).update(data, dbcommit, reindex)
return self

@classmethod
def _acq_order_line_build_org_ref(cls, data):
"""Build $ref for the organisation of the acquisition order."""
from ..acq_orders.api import AcqOrder

order_pid = data.get('acq_order', {}).get('pid')
if not order_pid:
order_pid = data.get('acq_order').get(
'$ref').split('acq_orders/')[1]

org_pid = AcqOrder.get_record_by_pid(order_pid).organisation_pid
base_url = current_app.config.get('RERO_ILS_APP_BASE_URL')
url_api = '{base_url}/api/{doc_type}/{pid}'
org_ref = {
'$ref': url_api.format(
base_url=base_url,
doc_type='organisations',
pid=org_pid or cls.organisation_pid)
}
data['organisation'] = org_ref

@classmethod
def _build_total_amount_for_order_line(cls, data):
"""Build total amount for order line."""
total_amount = data['amount'] * data['quantity']
if data['discount_amount']:
total_amount -= data['discount_amount']
data['total_amount'] = total_amount

@property
def order_pid(self):
"""Shortcut for acquisition order pid."""
return self.replace_refs().get('acq_order').get('pid')

@property
def organisation_pid(self):
"""Get organisation pid for acquisition order."""
return self.get_order().organisation_pid

@property
def library_pid(self):
"""Shortcut for acquisition order library pid."""
from ..acq_orders.api import AcqOrder
order = AcqOrder.get_record_by_pid(self.order_pid)
return order.library_pid

def get_organisation(self):
"""Shortcut to the organisation of the acquisition order."""
return self.get_library().get_organisation()

def get_library(self):
"""Shortcut to the library of the acquisition order."""
return self.get_order().get_library()

def get_order(self):
"""Shortcut to the order of the order line."""
from ..acq_orders.api import AcqOrder
return AcqOrder.get_record_by_pid(self.order_pid)

def get_number_of_acq_order_lines(self):
"""Get number of aquisition order lines."""
results = AcqOrderLinesSearch().filter(
'term', acq_order__pid=self.order_pid).source().count()
return results
38 changes: 38 additions & 0 deletions rero_ils/modules/acq_order_lines/jsonresolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2019 RERO
#
# 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/>.

"""Acquisition Order Line resolver."""

import jsonresolver
from flask import current_app
from invenio_pidstore.models import PersistentIdentifier, PIDStatus


@jsonresolver.route('/api/acq_order_lines/<pid>', host='ils.rero.ch')
def acq_order_line_resolver(pid):
"""Resolver for Acquisition Order Line record."""
persistent_id = PersistentIdentifier.get('acol', pid)
if persistent_id.status == PIDStatus.REGISTERED:
return dict(pid=persistent_id.pid_value)
current_app.logger.error(
'Doc resolver error: /api/acq_order_lines/{pid} {persistent_id}'
.format(
pid=pid,
persistent_id=persistent_id
)
)
raise Exception('unable to resolve')
20 changes: 20 additions & 0 deletions rero_ils/modules/acq_order_lines/jsonschemas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2019 RERO
#
# 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/>.

"""JSON schemas."""

from __future__ import absolute_import, print_function
Loading

0 comments on commit c89e90b

Please sign in to comment.