Skip to content

Commit

Permalink
PartonTransactionEvent: Fix decimal amount problem.
Browse files Browse the repository at this point in the history
* Creates an extension to check if event amount contains a maximum of 2
  decimals.
* Reorganizing some codes from `PatronTransaction` resource to be more
  readable/usable.
* Closes rero#2602.

Co-Authored-by: Renaud Michotte <[email protected]>
  • Loading branch information
zannkukai committed Feb 7, 2022
1 parent c2658a4 commit 6cc8d2f
Show file tree
Hide file tree
Showing 17 changed files with 426 additions and 339 deletions.
7 changes: 3 additions & 4 deletions rero_ils/modules/loans/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

from ..items.api import Item
from ..loans.logs.api import LoanOperationLog
from ..patron_transactions.api import PatronTransaction
from ..patron_transactions.utils import \
create_patron_transaction_from_overdue_loan


def enrich_loan_data(sender, json=None, record=None, index=None,
Expand Down Expand Up @@ -58,8 +59,6 @@ def listener_loan_state_changed(

# Create fees for checkin or extend operations
if trigger in ['checkin', 'extend']:
PatronTransaction.create_patron_transaction_from_overdue_loan(
initial_loan
)
create_patron_transaction_from_overdue_loan(initial_loan)

LoanOperationLog.create(loan)
4 changes: 3 additions & 1 deletion rero_ils/modules/notifications/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
from ..minters import id_minter
from ..patron_transactions.api import PatronTransaction, \
PatronTransactionsSearch
from ..patron_transactions.utils import \
create_patron_transaction_from_notification
from ..providers import Provider

# notification provider
Expand Down Expand Up @@ -102,7 +104,7 @@ def create(cls, data, id_=None, delete_pid=False, dbcommit=False,
data.setdefault('status', NotificationStatus.CREATED)
record = super().create(data, id_, delete_pid, dbcommit, reindex,
**kwargs)
PatronTransaction.create_patron_transaction_from_notification(
create_patron_transaction_from_notification(
notification=record, dbcommit=dbcommit, reindex=reindex,
delete_pid=delete_pid
)
Expand Down
32 changes: 18 additions & 14 deletions rero_ils/modules/patron_transaction_events/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@

from flask_babelex import gettext as _

from .extentions import DecimalAmountExtension
from .models import PatronTransactionEventIdentifier, \
PatronTransactionEventMetadata
PatronTransactionEventMetadata, PatronTransactionEventType
from ..api import IlsRecord, IlsRecordsIndexer, IlsRecordsSearch
from ..fetchers import id_fetcher
from ..minters import id_minter
Expand Down Expand Up @@ -76,15 +77,16 @@ class PatronTransactionEvent(IlsRecord):
}
}

_extensions = [
DecimalAmountExtension('amount')
]

@classmethod
def create(cls, data, id_=None, delete_pid=False,
dbcommit=False, reindex=False, update_parent=True, **kwargs):
"""Create patron transaction event record."""
if 'creation_date' not in data:
data['creation_date'] = datetime.now(timezone.utc).isoformat()
if 'amount' in data:
# ensure multiple of 0.01
data['amount'] = round(data['amount'], 2)
record = super().create(
data, id_, delete_pid, dbcommit, reindex, **kwargs)
if update_parent:
Expand Down Expand Up @@ -149,18 +151,20 @@ def update_parent_patron_transaction(self):
# digits, we can multiply amounts by 100, cast result as integer,
# do operation with these values, and (at the end) divide the result
# by 100.
patron_transaction = self.patron_transaction()
total_amount = int(patron_transaction.get('total_amount') * 100)
if self.event_type == 'fee':
total_amount += int(self.amount * 100)
elif self.event_type in ('payment', 'cancel'):
total_amount -= int(self.amount * 100)
patron_transaction['total_amount'] = total_amount / 100
pttr = self.patron_transaction
total_amount = int(pttr.get('total_amount') * 100)
amount = int(self.amount * 100)
if self.event_type == PatronTransactionEventType.FEE:
total_amount += amount
elif self.event_type in [PatronTransactionEventType.PAYMENT,
PatronTransactionEventType.CANCEL]:
total_amount -= amount
pttr['total_amount'] = total_amount / 100
if total_amount == 0:
patron_transaction['status'] = 'closed'
patron_transaction.update(
patron_transaction, dbcommit=True, reindex=True)
pttr['status'] = 'closed'
pttr.update(pttr, dbcommit=True, reindex=True)

@property
def patron_transaction(self):
"""Return the parent patron transaction of the event."""
from ..patron_transactions.api import PatronTransaction
Expand Down
67 changes: 67 additions & 0 deletions rero_ils/modules/patron_transaction_events/extentions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
#
# RERO ILS
# Copyright (C) 2022 RERO
# Copyright (C) 2022 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/>.

"""PatronTransactionEvent record extensions."""

from invenio_records.extensions import RecordExtension
from jsonschema import ValidationError


class DecimalAmountExtension(RecordExtension):
"""Check and transform a decimal amount into integer representation."""

def __init__(self, field_name, decimals=2):
"""Extension initialization.
:param field_name: the field name where found the amount to analyze.
:param decimals: the number of decimal accepted.
"""
self.amount_field = field_name
self.multiplier = pow(10, decimals)

def _check_amount(self, record):
"""Check if the record has the correct decimal number.
:param record: the record to validate.
:raise ValidationError if the amount doesn't respect the decimal amount
"""
# Check `self.amount_field` exists into the record. If not, we can
# return without record modification.
if self.amount_field not in record:
return

# Check that the amount field has the correct decimals. If not, a
# `ValidationError` error will be raised.
# NOTE:
# an amount of "123,450" is true despite if we configure only 2
# decimals. In same way "123,4" is also a valid value.
# DEV NOTE :
# rounding the number, keeping one decimal will prevent the floating
# numbers problem
# >>> 2.2 * 100 --> 220.00000000000003
# >>> round(2.2 * 100, 1) --> 220.0
# >>> round(2.234 * 100, 1) --> 223.4
# >>> round(2.2345 * 100, 1) --> 223.5
quotient = round(record[self.amount_field] * self.multiplier, 1)
if (quotient % 1) != 0:
decimal = 1 / self.multiplier
msg = f'`{self.amount_field}` must be multiple of {decimal}'
raise ValidationError(msg)

pre_commit = _check_amount
pre_create = _check_amount
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
},
"amount": {
"title": "Amount",
"description": "The fee amount as cents",
"type": "number",
"exclusiveMinimum": 0,
"multipleOf": 0.01
"exclusiveMinimum": 0
},
"steps": {
"title": "Steps",
Expand All @@ -60,8 +60,7 @@
},
"amount": {
"type": "number",
"exclusiveMinimum": 0,
"multipleOf": 0.01
"exclusiveMinimum": 0
}
},
"required": [
Expand Down
9 changes: 9 additions & 0 deletions rero_ils/modules/patron_transaction_events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ class PatronTransactionEventMetadata(db.Model, RecordMetadataBase):
"""PatronTransactionEvent record metadata."""

__tablename__ = 'patron_transaction_event_metadata'


class PatronTransactionEventType:
"""Type of PatronTransactionEvent."""

FEE = 'fee'
PAYMENT = 'payment'
DISPUTE = 'dispute'
CANCEL = 'cancel'
Loading

0 comments on commit 6cc8d2f

Please sign in to comment.