Skip to content

Commit

Permalink
Merge pull request #210 from amichard/historical-data-frontend
Browse files Browse the repository at this point in the history
Historical data frontend
  • Loading branch information
kuanfandevops authored Apr 11, 2018
2 parents a6c12b0 + df1f42c commit 651ee8d
Show file tree
Hide file tree
Showing 17 changed files with 608 additions and 133 deletions.
18 changes: 18 additions & 0 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,24 @@ class Meta:
class CreditTradeUpdateSerializer(serializers.ModelSerializer):

def validate(self, data):
if (data.get('fair_market_value_per_credit') == 0 and
data.get('zero_reason') is None):
allowed_types = list(
CreditTradeType.objects
.filter(the_type__in=[
"Credit Validation", "Credit Retirement", "Part 3 Award"
])
.only('id')
)

credit_trade_type = data.get('type')

if credit_trade_type not in allowed_types:
raise serializers.ValidationError({
'zeroDollarReason': 'Zero Dollar Reason is required '
'for Credit Transfers with 0 Dollar per Credit'
})

return data

class Meta:
Expand Down
89 changes: 83 additions & 6 deletions backend/api/services/CreditTradeService.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from api.exceptions import PositiveIntegerException
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.db import transaction

import datetime

Expand Down Expand Up @@ -137,23 +138,26 @@ def approve(credit_trade):
return credit_trade

@staticmethod
@transaction.non_atomic_requests()
def transfer_credits(_from, _to, credit_trade_id, num_of_credits,
effective_date):
from_starting_bal = OrganizationBalance.objects.get(
from_starting_bal, created = OrganizationBalance.objects.get_or_create(
organization_id=_from.id,
expiration_date=None)
expiration_date=None,
defaults={'validated_credits': 0})

to_starting_bal = OrganizationBalance.objects.get(
to_starting_bal, created = OrganizationBalance.objects.get_or_create(
organization_id=_to.id,
expiration_date=None)
expiration_date=None,
defaults={'validated_credits': 0})

# Compute for end balance
from_credits = from_starting_bal.validated_credits - num_of_credits
to_credits = to_starting_bal.validated_credits + num_of_credits

if 0 > from_credits:
if from_credits < 0:
raise PositiveIntegerException("Can't complete transaction,"
"insufficient credits")
"`{}` has insufficient credits".format(_from.name))

# Update old balance effective date
from_starting_bal.expiration_date = effective_date
Expand All @@ -180,3 +184,76 @@ def transfer_credits(_from, _to, credit_trade_id, num_of_credits,

from_new_bal.save()
to_new_bal.save()

@staticmethod
def validate_credits(credit_trades):
errors = []
temp_storage = []

for credit_trade in credit_trades:
from_starting_index, from_starting_balance = CreditTradeService. \
get_temp_balance(temp_storage, credit_trade.credits_from.id)

to_starting_index, to_starting_balance = CreditTradeService. \
get_temp_balance(temp_storage, credit_trade.credits_to.id)

from_credits_remaining = from_starting_balance - \
credit_trade.number_of_credits

to_credits_remaining = to_starting_balance + \
credit_trade.number_of_credits

CreditTradeService.update_temp_balance(
temp_storage,
from_starting_index,
from_credits_remaining,
credit_trade.credits_from.id)

CreditTradeService.update_temp_balance(
temp_storage,
to_starting_index,
to_credits_remaining,
credit_trade.credits_to.id)

if from_credits_remaining < 0:
errors.append(
"[ID: {}] "
"Can't complete transaction,"
"`{}` has insufficient credits.".
format(credit_trade.id, credit_trade.credits_from.name))

if len(errors) > 0:
raise PositiveIntegerException(errors)

@staticmethod
def get_temp_balance(storage, id):
starting_balance = None
index = None

if len(storage) > 0:
for balance_index, balance in enumerate(storage):
if balance["id"] == id:
starting_balance = balance["credits"]
index = balance_index

if starting_balance is None:
try: # if balance hasn't been populated, get from the database
organization_balance = OrganizationBalance.objects.get(
organization_id=id,
expiration_date=None)

starting_balance = organization_balance.validated_credits
except OrganizationBalance.DoesNotExist:
starting_balance = 0

return index, starting_balance

@staticmethod
def update_temp_balance(storage, index, credits, id):
if index is None:
storage.append({
"id": id,
"credits": credits
})
else:
storage[index]["credits"] = credits
240 changes: 240 additions & 0 deletions backend/api/test_credit_trades.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
from django.test import TestCase, Client
from rest_framework import status

from api.exceptions import PositiveIntegerException

from api.models.CreditTrade import CreditTrade
from api.models.CreditTradeStatus import CreditTradeStatus
from api.models.CreditTradeType import CreditTradeType
from api.models.CreditTradeZeroReason import CreditTradeZeroReason
from api.models.Organization import Organization
from api.models.OrganizationBalance import OrganizationBalance
from api.models.User import User

from api.services.CreditTradeService import CreditTradeService

# Credit Trade Statuses
STATUS_DRAFT = 1
STATUS_SUBMITTED = 2
Expand Down Expand Up @@ -199,3 +206,236 @@ def test_government_user_add_credit_transfer(self):

# 400 since zero reason was set to None
assert response.status_code == status.HTTP_201_CREATED

# As a government user, I should be able to validate approved credit
# transfers:
# It should raise an exception if it sees any fuel suppliers with
# insufficient funds
def test_validate_credit(self):
credit_trade_status, created = CreditTradeStatus.objects.get_or_create(
status='Approved')

credit_trade_type, created = CreditTradeType.objects.get_or_create(
the_type='Sell')

credit_trade_zero_reason, created = CreditTradeZeroReason.objects \
.get_or_create(reason='Other', display_order=2)

CreditTrade.objects.create(status=credit_trade_status,
initiator=self.user_2.organization,
respondent=self.user_3.organization,
type=credit_trade_type,
number_of_credits=1000000000,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

credit_trades = CreditTrade.objects.filter(
status_id=credit_trade_status.id)

with self.assertRaises(PositiveIntegerException):
CreditTradeService.validate_credits(credit_trades)

# As a government user, I should be able to validate approved credit
# transfers:
# It should raise an exception if it sees any fuel suppliers with
# insufficient funds
# This is a slightly more complex test where we have multi credit trades
# with new organizations that bounces the number of credits up and down
def test_validate_credit_complex(self):
credit_trade_status, created = CreditTradeStatus.objects.get_or_create(
status='Approved')

credit_trade_type, created = CreditTradeType.objects.get_or_create(
the_type='Sell')

credit_trade_zero_reason, created = CreditTradeZeroReason.objects \
.get_or_create(reason='Other', display_order=2)

from_organization = Organization.objects.create(
name="Test 1",
actions_type_id=1,
status_id=1)
to_organization = Organization.objects.create(
name="Test 2",
actions_type_id=1,
status_id=1)

# Award Test 1 with 1000 credits (new organizations start
# with 0 credits)
# (Please note in most cases we should use a different type
# but to reduce the number of things to keep track, lets just
# transfer from organization: 1 (BC Government))
CreditTrade.objects.create(status=credit_trade_status,
initiator=self.gov_user.organization,
respondent=from_organization,
type=credit_trade_type,
number_of_credits=1000,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

# Transfer 500 from Test 1 to Test 2
CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=500,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

# Transfer 700 from Test 1 to Test 2
CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=700,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

credit_trades = CreditTrade.objects.filter(
status_id=credit_trade_status.id)

# this should now raise an exception since we tried transferring
# 1200 credits when only 1000 are available
with self.assertRaises(PositiveIntegerException):
CreditTradeService.validate_credits(credit_trades)

# As a government user, I should be able to validate approved credit
# transfers:
# It should raise an exception if it sees any fuel suppliers with
# insufficient funds
# This test is similar to the one above, but should succeed as we're going
# to allocate the right amount of credits this time
def test_validate_credit_success(self):
credit_trade_status, created = CreditTradeStatus.objects.get_or_create(
status='Approved')

credit_trade_type, created = CreditTradeType.objects.get_or_create(
the_type='Sell')

credit_trade_zero_reason, created = CreditTradeZeroReason.objects \
.get_or_create(reason='Other', display_order=2)

from_organization = Organization.objects.create(
name="Test 1",
actions_type_id=1,
status_id=1)
to_organization = Organization.objects.create(
name="Test 2",
actions_type_id=1,
status_id=1)

# Award Test 1 with 1000 credits (new organizations start
# with 0 credits)
# (Please note in most cases we should use a different type
# but to reduce the number of things to keep track, lets just
# transfer from organization: 1 (BC Government))
CreditTrade.objects.create(status=credit_trade_status,
initiator=self.gov_user.organization,
respondent=from_organization,
type=credit_trade_type,
number_of_credits=1000,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

# Transfer 500 from Test 1 to Test 2
CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=500,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

# Transfer 300 from Test 1 to Test 2
CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=500,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

credit_trades = CreditTrade.objects.filter(
status_id=credit_trade_status.id)

# no exceptions should be raised
CreditTradeService.validate_credits(credit_trades)

# As a government user, I should be able to process all the approved
# credit transfers
# This test is similar to the one above, but a functional test to check
# if the commit actually works
def test_batch_process(self):
credit_trade_status, created = CreditTradeStatus.objects.get_or_create(
status='Approved')

credit_trade_type, created = CreditTradeType.objects.get_or_create(
the_type='Sell')

credit_trade_zero_reason, created = CreditTradeZeroReason.objects \
.get_or_create(reason='Other', display_order=2)

from_organization = Organization.objects.create(
name="Test 1",
actions_type_id=1,
status_id=1)
to_organization = Organization.objects.create(
name="Test 2",
actions_type_id=1,
status_id=1)

CreditTrade.objects.create(status=credit_trade_status,
initiator=self.gov_user.organization,
respondent=from_organization,
type=credit_trade_type,
number_of_credits=1000,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=500,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

CreditTrade.objects.create(status=credit_trade_status,
initiator=from_organization,
respondent=to_organization,
type=credit_trade_type,
number_of_credits=400,
fair_market_value_per_credit=0,
zero_reason=credit_trade_zero_reason,
trade_effective_date=datetime.datetime
.today().strftime('%Y-%m-%d'))

credit_trades = CreditTrade.objects.filter(
status_id=credit_trade_status.id)

response = self.gov_client.put('/api/credit_trades/batch_process')
assert response.status_code == status.HTTP_200_OK

organization_balance = OrganizationBalance.objects.get(
organization_id=from_organization.id,
expiration_date=None)

assert organization_balance.validated_credits == 100
Loading

0 comments on commit 651ee8d

Please sign in to comment.