def approve(self, request, pk=None): """ Marks the Credit Trade as Approved Transfers the Credits Then, marks the Credit Trade as Completed """ credit_trade = self.get_object() credit_trade.trade_effective_date = datetime.date.today() previous_state = credit_trade if credit_trade.compliance_period_id is None: credit_trade.compliance_period_id = \ CreditTradeService.get_compliance_period_id(credit_trade) serializer = self.get_serializer(credit_trade, data=request.data) serializer.is_valid(raise_exception=True) completed_credit_trade = CreditTradeService.approve( credit_trade, request.user ) serializer = self.get_serializer(completed_credit_trade) CreditTradeService.dispatch_notifications(previous_state, completed_credit_trade) return Response(serializer.data, status=status.HTTP_200_OK)
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)
def test_validate_credit_success(self): """ 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 """ credit_trades = [] # 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)) credit_trades.append( CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.users['gov_analyst'].organization, respondent=self.organizations['from'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d'))) # Transfer 500 from Test 1 to Test 2 credit_trades.append( CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=500, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d'))) # Transfer 300 from Test 1 to Test 2 credit_trades.append( CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=300, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d'))) # no exceptions should be raised CreditTradeService.validate_credits(credit_trades)
def perform_update(self, serializer): previous_state = self.get_object() credit_trade = serializer.save() CreditTradeService.create_history(credit_trade, False) status_cancelled = CreditTradeStatus.objects.get(status="Cancelled") if serializer.data['status'] != status_cancelled.id: CreditTradeService.dispatch_notifications(previous_state, credit_trade)
def batch_process(self, request): status_approved = CreditTradeStatus.objects \ .get(status="Approved") credit_trades = CreditTrade.objects.filter( status_id=status_approved.id) for credit_trade in credit_trades: CreditTradeService.approve(credit_trade) return Response(None, status=status.HTTP_200_OK)
def has_permission(self, request, view): """Check permissions When an object does not yet exist (POST)""" # Fallback to has_object_permission unless it's a POST if request.method != 'POST': return True # Need this information to make a decision if not (('privileged_access' in request.data) and ('credit_trade' in request.data)): return False credit_trade = request.data['credit_trade'] privileged_access = request.data['privileged_access'] # Check if the user is a party to this credit_trade (or Government) # using CreditTradeService logic found = CreditTradeService.get_organization_credit_trades(request.user.organization) \ .filter(id=credit_trade).first() if not found: return False return CreditTradeCommentPermissions.user_can_comment( request.user, found, privileged_access )
def batch_process(self, request): status_approved = CreditTradeStatus.objects \ .get(status="Approved") credit_trades = CreditTrade.objects.filter( status_id=status_approved.id).order_by('id') CreditTradeService.validate_credits(credit_trades) for credit_trade in credit_trades: credit_trade.update_user_id = request.user.id CreditTradeService.approve(credit_trade) return Response( {"message": "Approved Credit Transactions have been processed."}, status=status.HTTP_200_OK)
def approve(self, request, pk=None): credit_trade = self.get_object() completed_credit_trade = CreditTradeService.approve(credit_trade) serializer = self.get_serializer(completed_credit_trade) return Response(serializer.data, status=status.HTTP_200_OK)
def test_validate_credit_complex(self): """ As a government user, I should be able to validate recorded 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 """ initial_balance = OrganizationBalance.objects.get( organization_id=self.organizations['from'].id, expiration_date=None).validated_credits # Transfer initial balance from Test 1 to Test 2 CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=initial_balance, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # Transfer 1 from Test 1 to Test 2 CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTrade.objects.filter( status_id=self.statuses['recorded'].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)
def get_queryset(self): """ This view should return a list of all the purchases for the currently authenticated user. """ user = self.request.user return CreditTradeService.get_organization_credit_trades( user.organization)
def approve(self, request, pk=None): credit_trade = self.get_object() credit_trade.trade_effective_date = datetime.date.today() serializer = self.get_serializer(credit_trade, data=request.data) serializer.is_valid(raise_exception=True) completed_credit_trade = CreditTradeService.approve(credit_trade) serializer = self.get_serializer(completed_credit_trade) return Response(serializer.data, status=status.HTTP_200_OK)
def test_validate_credit(self): """ As a government user, I should be able to validate recorded credit transfers: It should raise an exception if it sees any fuel suppliers with insufficient funds """ CreditTrade.objects.create( status=self.statuses['recorded'], initiator=self.users['fs_user_2'].organization, respondent=self.users['fs_user_3'].organization, type=self.credit_trade_types['sell'], number_of_credits=1000000000, fair_market_value_per_credit=0, zero_reason=self.zero_reason['other'], trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTrade.objects.filter( status_id=self.statuses['recorded'].id) with self.assertRaises(PositiveIntegerException): CreditTradeService.validate_credits(credit_trades)
def test_get_organization_credit_trades_gov(self): """ As a government user I shouldn't see drafts unless I'm the initiator I shouldn't see cancelled transfers as they're considered (deleted) """ # the function shouldn't see this as it's only a draft and the # initiator is not government draft_credit_trade = CreditTrade.objects.create( status=self.statuses['draft'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a draft from the government draft_credit_trade_from_gov = CreditTrade.objects.create( status=self.statuses['draft'], initiator=self.users['gov_analyst'].organization, respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's approved approved_credit_trade = CreditTrade.objects.create( status=self.statuses['approved'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTradeService.get_organization_credit_trades( self.users['gov_analyst'].organization) self.assertNotIn(draft_credit_trade, credit_trades) self.assertIn(draft_credit_trade_from_gov, credit_trades) self.assertIn(approved_credit_trade, credit_trades)
def has_permission(self, request, view): """Check permissions When an object does not yet exist (POST)""" if not request.user.has_perm('SIGN_CREDIT_TRANSFER'): return False if isinstance(request.data, list): to_check = request.data else: to_check = [request.data] if len(to_check) == 0: return False # Need this information to make a decision for obj in to_check: if 'credit_trade' in obj: credit_trade = obj['credit_trade'] # Check if the user is a party to this credit_trade # (or Government) using CreditTradeService logic found = CreditTradeService.get_organization_credit_trades( request.user.organization).filter(id=credit_trade).first() if not found: return False if not SigningAuthorityConfirmationPermissions.user_can_sign( request.user, found): return False elif 'compliance_report' in obj: # check that the compliance report does exist and the user can # sign it compliance_report = obj['compliance_report'] found = ComplianceReport.objects.filter( id=compliance_report, organization=request.user.organization) if not found: return False else: # Neither credit trade or compliance report was provided return False return True
def has_permission(self, request, view): """Check permissions When an object does not yet exist (POST)""" if not request.user.has_perm('SIGN_CREDIT_TRANSFER'): return False if isinstance(request.data, list): to_check = request.data else: to_check = [request.data] if len(to_check) == 0: return False # Need this information to make a decision for obj in to_check: if 'credit_trade' not in obj: return False credit_trade = obj['credit_trade'] # Check if the user is a party to this credit_trade (or Government) # using CreditTradeService logic found = CreditTradeService.get_organization_credit_trades(request.user.organization) \ .filter(id=credit_trade).first() if not found: return False if not SigningAuthorityConfirmationPermissions.user_can_sign( request.user, found): return False return True
def batch_process(self, request): """ Call the approve function on multiple Credit Trades """ status_approved = CreditTradeStatus.objects \ .get(status="Recorded") credit_trades = CreditTrade.objects.filter( status_id=status_approved.id).order_by('id') CreditTradeService.validate_credits(credit_trades) for credit_trade in credit_trades: credit_trade.update_user_id = request.user.id CreditTradeService.approve(credit_trade) CreditTradeService.dispatch_notifications(None, credit_trade) return Response( {"message": "Approved credit transactions have been processed."}, status=status.HTTP_200_OK)
def perform_update(self, serializer): credit_trade = serializer.save() CreditTradeService.create_history(credit_trade, False)
def validate(self, data): """ Makes sure that the status the credit trade is being updated to is valid. There are certain states that should lock a credit trade from being modified. """ request = self.context['request'] available_statuses = [] if self.instance.is_rescinded: raise serializers.ValidationError({ 'readOnly': "Cannot update a transaction that's already " "been rescinded." }) if self.instance.status.status in [ "Approved", "Cancelled", "Declined", "Refused" ]: raise serializers.ValidationError({ 'readOnly': "Cannot update a transaction that's already " "been `{}`.".format(self.instance.status.status) }) # if the user is the respondent, they really shouldn't be modifying # other fields. So reset those to be sure that they weren't changed if self.instance.respondent == request.user.organization: orig_data = data data = { 'compliance_period': self.instance.compliance_period, 'fair_market_value_per_credit': self.instance.fair_market_value_per_credit, 'initiator': self.instance.initiator, 'is_rescinded': bool(data.get('is_rescinded')), 'number_of_credits': self.instance.number_of_credits, 'respondent': self.instance.respondent, 'status': data.get('status'), 'type': self.instance.type, 'update_user': request.user, 'zero_reason': self.instance.zero_reason } # Preserve the comment, if they are making one if 'comment' in orig_data: data['comment'] = orig_data['comment'] # if status is being modified, make sure the next state is valid if 'status' in request.data: credit_trade_status = data.get('status') if not data.get('is_rescinded') is True: available_statuses = CreditTradeService.get_allowed_statuses( self.instance, request) allowed_statuses = list( CreditTradeStatus.objects.filter( status__in=available_statuses).only('id')) if credit_trade_status not in allowed_statuses: raise serializers.ValidationError({ 'invalidStatus': "You do not have permission to set " "the status to `{}`.".format( credit_trade_status.status) }) if (credit_trade_status != self.instance.status and data.get('is_rescinded') is True): raise serializers.ValidationError({ 'invalidStatus': "Cannot update status and rescind at the " "same time." }) will_create_a_comment = True if 'comment' in data \ and data['comment'] is not None \ and len(data['comment'].strip()) > 0 else False if credit_trade_status.status == 'Submitted': zero_reason = data.get('zero_reason') if zero_reason is not None and zero_reason.reason == 'Other': if not (will_create_a_comment or CreditTradeComment.objects.filter( credit_trade_id=self.instance.id, create_user__organization=request.user. organization).exists()): raise serializers.ValidationError({ 'forbidden': "Please provide an explanation in " "the comments as to why the Credit " "Transfer Proposal has a fair market " "value of zero dollars per credit." }) if data.get('is_rescinded') is True: if request.user.organization not in [ self.instance.initiator, self.instance.respondent ]: raise serializers.ValidationError({ 'forbidden': "Cannot rescind unless organization is part " "of the proposal." }) if self.instance.status.status == 'Draft': raise serializers.ValidationError( {'forbidden': "Cannot rescind a draft"}) if (self.instance.status.status == 'Submitted' and self.instance.respondent == request.user.organization): raise serializers.ValidationError({ 'forbidden': "Cannot rescind a proposed trade when you're " " the respondent" }) if data.get('fair_market_value_per_credit') is not None and \ data.get('fair_market_value_per_credit') > 0 and \ data.get('zero_reason') is not None: raise serializers.ValidationError({ 'zeroDollarReason': "Zero dollar reason supplied but this " "trade has a non-zero value-per-credit" }) 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 Reduction", "Part 3 Award" ]).only('id')) credit_trade_type = data.get('type') if credit_trade_type not in allowed_types: raise serializers.ValidationError({ 'zeroDollarReason': "Please select a reason as to " "why the Credit Transfer Proposal " "has a fair market value of zero " "dollars per credit. " }) # If the type is a sell, make sure that the organization # selling has enough credits, before they try to save the draft # or propose # If the type is buy, make sure the organization the credit is # coming from has enough credits before they can accept the proposal balance = request.user.organization.organization_balance[ 'validated_credits'] buy_type = CreditTradeType.objects.get(the_type="Buy") sell_type = CreditTradeType.objects.get(the_type="Sell") if 'type' in data: credit_trade_type = data.get('type') else: credit_trade_type = self.instance.type if 'number_of_credits' in data: number_of_credits = data.get('number_of_credits') else: number_of_credits = self.instance.number_of_credits previous_state = CreditTrade.objects.get(id=self.instance.id) if 'comment' in data and data['comment'] is not None and \ len(data['comment'].strip()) > 0: if 'ADD_COMMENT' not in CreditTradeCommentActions.\ available_comment_actions(request, previous_state): raise serializers.ValidationError( "Cannot add a comment in this state") accepted_status = CreditTradeStatus.objects.get(status="Accepted") draft_propose_statuses = list( CreditTradeStatus.objects.filter( status__in=["Draft", "Submitted"]).only('id')) if (self.instance.initiator == request.user.organization and credit_trade_status in draft_propose_statuses and credit_trade_type == sell_type) or \ (self.instance.respondent == request.user.organization and credit_trade_status == accepted_status and credit_trade_type == buy_type): if balance < number_of_credits: raise serializers.ValidationError({ 'insufficientCredits': "{} does not have enough credits " "for the proposal.".format(request.user.organization.name) }) return data
def test_get_organization_credit_trades_fuel_supplier(self): completed_status, created = CreditTradeStatus.objects.get_or_create( status='Completed') cancelled_status, created = CreditTradeStatus.objects.get_or_create( status='Cancelled') draft_status, created = CreditTradeStatus.objects.get_or_create( status='Draft') submitted_status, created = CreditTradeStatus.objects.get_or_create( status='Submitted') credit_trade_type, created = CreditTradeType.objects.get_or_create( the_type='Sell') test_organization_1 = Organization.objects.create(name="Test 1", actions_type_id=1, status_id=1) test_organization_2 = Organization.objects.create(name="Test 2", actions_type_id=1, status_id=1) test_organization_3 = Organization.objects.create(name="Test 3", actions_type_id=1, status_id=1) # the function shouldn't see this as it's only a draft and the # initiator is not fuel_supplier # (even though the fuel supplier is the respondent) draft_credit_trade = CreditTrade.objects.create( status=draft_status, initiator=test_organization_2, respondent=test_organization_1, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a draft from the fuel supplier draft_credit_trade_from_fuel_supplier = CreditTrade.objects.create( status=draft_status, initiator=test_organization_1, respondent=test_organization_2, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function shouldn't see this as it's a submitted transaction # not involving the fuel supplier submitted_credit_trade = CreditTrade.objects.create( status=submitted_status, initiator=test_organization_2, respondent=test_organization_3, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a submitted transaction # involving the fuel supplier submitted_credit_trade_as_respondent = CreditTrade.objects.create( status=submitted_status, initiator=test_organization_2, respondent=test_organization_1, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's completed completed_credit_trade = CreditTrade.objects.create( status=completed_status, initiator=test_organization_1, respondent=test_organization_2, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTradeService.get_organization_credit_trades( test_organization_1) self.assertNotIn(draft_credit_trade, credit_trades) self.assertIn(draft_credit_trade_from_fuel_supplier, credit_trades) self.assertNotIn(submitted_credit_trade, credit_trades) self.assertIn(submitted_credit_trade_as_respondent, credit_trades) self.assertIn(completed_credit_trade, credit_trades)
def test_get_organization_credit_trades_gov(self): completed_status, created = CreditTradeStatus.objects.get_or_create( status='Completed') cancelled_status, created = CreditTradeStatus.objects.get_or_create( status='Cancelled') draft_status, created = CreditTradeStatus.objects.get_or_create( status='Draft') credit_trade_type, created = CreditTradeType.objects.get_or_create( the_type='Sell') 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) # the function shouldn't see this as it's only a draft and the # initiator is not government draft_credit_trade = CreditTrade.objects.create( status=draft_status, initiator=from_organization, respondent=to_organization, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a draft from the government draft_credit_trade_from_gov = CreditTrade.objects.create( status=draft_status, initiator=self.gov_user.organization, respondent=to_organization, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's completed completed_credit_trade = CreditTrade.objects.create( status=completed_status, initiator=from_organization, respondent=to_organization, type=credit_trade_type, number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTradeService.get_organization_credit_trades( self.gov_user.organization) self.assertNotIn(draft_credit_trade, credit_trades) self.assertIn(draft_credit_trade_from_gov, credit_trades) self.assertIn(completed_credit_trade, credit_trades)
def test_validate_credit_success(self): credit_trades = [] 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)) credit_trades.append( 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 credit_trades.append( 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 credit_trades.append( CreditTrade.objects.create( status=credit_trade_status, initiator=from_organization, respondent=to_organization, type=credit_trade_type, number_of_credits=300, fair_market_value_per_credit=0, zero_reason=credit_trade_zero_reason, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d'))) # no exceptions should be raised CreditTradeService.validate_credits(credit_trades)
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)
def test_get_organization_credit_trades_fuel_supplier(self): """ As a fuel supplier I shouldn't see drafts unless I'm the initiator I shouldn't see cancelled transfers as they're considered (deleted) I shouldn't see submitted transfers unless I'm involved somehow """ # the function shouldn't see this as it's only a draft and the # initiator is not fuel_supplier # (even though the fuel supplier is the respondent) draft_credit_trade = CreditTrade.objects.create( status=self.statuses['draft'], initiator=self.organizations['to'], respondent=self.organizations['from'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a draft from the fuel supplier draft_from_fuel_supplier = CreditTrade.objects.create( status=self.statuses['draft'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function shouldn't see this as it's a submitted transaction # not involving the fuel supplier submitted_credit_trade = CreditTrade.objects.create( status=self.statuses['submitted'], initiator=self.organizations['to'], respondent=self.users['fs_user_3'].organization, type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's a submitted transaction # involving the fuel supplier credit_trade_as_respondent = CreditTrade.objects.create( status=self.statuses['submitted'], initiator=self.organizations['to'], respondent=self.organizations['from'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) # the function should see this as it's approved approved_credit_trade = CreditTrade.objects.create( status=self.statuses['approved'], initiator=self.organizations['from'], respondent=self.organizations['to'], type=self.credit_trade_types['sell'], number_of_credits=1000, fair_market_value_per_credit=1, zero_reason=None, trade_effective_date=datetime.datetime.today().strftime( '%Y-%m-%d')) credit_trades = CreditTradeService.get_organization_credit_trades( self.organizations['from']) self.assertNotIn(draft_credit_trade, credit_trades) self.assertIn(draft_from_fuel_supplier, credit_trades) self.assertNotIn(submitted_credit_trade, credit_trades) self.assertIn(credit_trade_as_respondent, credit_trades) self.assertIn(approved_credit_trade, credit_trades)
def perform_create(self, serializer): credit_trade = serializer.save() CreditTradeService.create_history(credit_trade, True) CreditTradeService.dispatch_notifications(None, credit_trade)
def create_director_transactions(compliance_report, creating_user): """ Validate or Reduce credits when the director accepts a compliance report Always use the snapshot as the basis for calculation, so we don't recompute anything and possibly alter the values :param compliance_report: :return: """ previous_transactions = [] current = compliance_report while current.supplements is not None: current = current.supplements if current.credit_transaction is not None: previous_transactions.append(current.credit_transaction) total_previous_reduction = Decimal(0.0) total_previous_validation = Decimal(0.0) for transaction in previous_transactions: if transaction.type.the_type in ['Credit Validation']: total_previous_validation += transaction.number_of_credits if transaction.type.the_type in ['Credit Reduction']: total_previous_reduction += transaction.number_of_credits if settings.DEVELOPMENT: print('we have {} previous transactions to consider'.format( len(previous_transactions))) print('Total of previous reductions: {}'.format( total_previous_reduction)) print('Total of previous validations: {}'.format( total_previous_validation)) if compliance_report.snapshot is None: raise InvalidStateException() snapshot = compliance_report.snapshot if 'summary' not in snapshot: raise InvalidStateException() if 'lines' not in snapshot['summary']: raise InvalidStateException() lines = snapshot['summary']['lines'] desired_net_credit_balance_change = Decimal(0.0) if Decimal(lines['25']) > Decimal(0): desired_net_credit_balance_change = Decimal(lines['25']) else: if Decimal(lines['25']) < 0 and Decimal(lines['26']) > Decimal(0): desired_net_credit_balance_change = Decimal( lines['26']) * Decimal(-1.0) required_credit_transaction = desired_net_credit_balance_change - \ (total_previous_validation - total_previous_reduction) if settings.DEVELOPMENT: print('line 25 of current report: {}'.format(lines['25'])) print('desired credit balance change: {}'.format( desired_net_credit_balance_change)) print('required transaction to effect change: {}'.format( required_credit_transaction)) if required_credit_transaction > Decimal(0): # do validation for Decimal(lines['25']) credit_transaction = CreditTrade( initiator=Organization.objects.get(id=1), respondent=compliance_report.organization, status=CreditTradeStatus.objects.get(status='Draft'), type=CreditTradeType.objects.get(the_type='Credit Validation'), number_of_credits=required_credit_transaction, compliance_period=compliance_report.compliance_period, create_user=creating_user, update_user=creating_user) credit_transaction.save() credit_transaction.refresh_from_db() CreditTradeService.approve(credit_transaction) compliance_report.credit_transaction = credit_transaction compliance_report.save() CreditTradeService.pvr_notification(None, credit_transaction) else: if required_credit_transaction < Decimal(0): # do_reduction for Decimal(lines['26']) credit_transaction = CreditTrade( initiator=Organization.objects.get(id=1), respondent=compliance_report.organization, status=CreditTradeStatus.objects.get(status='Draft'), type=CreditTradeType.objects.get( the_type='Credit Reduction'), number_of_credits=required_credit_transaction * Decimal(-1.0), compliance_period=compliance_report.compliance_period, create_user=creating_user, update_user=creating_user) credit_transaction.save() credit_transaction.refresh_from_db() CreditTradeService.approve(credit_transaction) compliance_report.credit_transaction = credit_transaction compliance_report.save() CreditTradeService.pvr_notification(None, credit_transaction)