diff --git a/backend/api/serializers/credit_transfer.py b/backend/api/serializers/credit_transfer.py index d08613177..f39250941 100644 --- a/backend/api/serializers/credit_transfer.py +++ b/backend/api/serializers/credit_transfer.py @@ -19,7 +19,7 @@ from api.serializers.credit_transfer_content import \ CreditTransferContentSerializer, CreditTransferContentSaveSerializer from api.serializers.user import UserBasicSerializer -from api.serializers.organization import OrganizationNameSerializer +from api.serializers.organization import OrganizationNameSerializer, OrganizationSerializer from api.services.credit_transaction import calculate_insufficient_credits from api.services.send_email import notifications_credit_transfers @@ -200,6 +200,15 @@ class Meta: ) +class CreditTransferOrganizationBalancesSerializer(ModelSerializer): + credit_to = OrganizationSerializer() + debit_from = OrganizationSerializer() + + class Meta: + model = CreditTransfer + fields = ('credit_to', 'debit_from') + + class CreditTransferSaveSerializer(ModelSerializer): """ Serializer to create a transfer diff --git a/backend/api/serializers/organization.py b/backend/api/serializers/organization.py index 95e40f6ba..eb17e53de 100644 --- a/backend/api/serializers/organization.py +++ b/backend/api/serializers/organization.py @@ -16,7 +16,7 @@ class OrganizationNameSerializer(serializers.ModelSerializer): class Meta: model = Organization fields = ( - 'name', 'short_name', 'is_government' + 'id', 'name', 'short_name', 'is_government' ) diff --git a/backend/api/tests/test_credit_transfers.py b/backend/api/tests/test_credit_transfers.py index 26f463507..f25acb89b 100644 --- a/backend/api/tests/test_credit_transfers.py +++ b/backend/api/tests/test_credit_transfers.py @@ -14,6 +14,7 @@ from ..models.organization import Organization from ..models.signing_authority_confirmation import SigningAuthorityConfirmation from ..models.signing_authority_assertion import SigningAuthorityAssertion +from ..models.credit_transfer_statuses import CreditTransferStatuses from unittest.mock import patch @@ -26,19 +27,19 @@ def setUp(self): gov_user = self.users['RTAN'].organization - transfer = CreditTransfer.objects.create( + self.transfer = CreditTransfer.objects.create( status='SUBMITTED', credit_to=org1, debit_from=org2, ) - transfer2 = CreditTransfer.objects.create( + self.transfer2 = CreditTransfer.objects.create( status='DRAFT', credit_to=org1, debit_from=org2, ) - transfer3 = CreditTransfer.objects.create( + self.transfer3 = CreditTransfer.objects.create( status='APPROVED', credit_to=org1, debit_from=org2, @@ -180,3 +181,47 @@ def test_credit_transfer_create(self): # Test that email method is called properly mock_send_credit_transfer_emails.assert_called() + + + def test_get_transfer(self): + transfer = self.transfer + transfer_initial_status = transfer.status + transfer_id = transfer.id + users = ["EMHILLIE_BCEID", "RTAN"] + for status in CreditTransferStatuses: + transfer.status = status + transfer.save() + for user in users: + response = self.clients[user].get("/api/credit-transfers/" + str(transfer_id)) + data = response.data + credit_to = data.get("credit_to") + debit_from = data.get("debit_from") + if credit_to is not None: + self.assertEqual(len(credit_to), 4) + self.assertFalse("balance" in credit_to or "ldv_sales" in credit_to or "avg_ldv_sales" in credit_to) + if debit_from is not None: + self.assertEqual(len(debit_from), 4) + self.assertFalse("balance" in debit_from or "ldv_sales" in debit_from or "avg_ldv_sales" in debit_from) + transfer.status = transfer_initial_status + transfer.save() + + + def test_get_org_balances(self): + gov_user = "RTAN" + non_gov_user = "EMHILLIE_BCEID" + transfer = self.transfer + transfer_initial_status = transfer.status + transfer_id = transfer.id + for user in (gov_user, non_gov_user): + for status in CreditTransferStatuses: + transfer.status = status + transfer.save() + response = self.clients[user].get("/api/credit-transfers/" + str(transfer_id) + "/org_balances") + response_status = response.status_code + data = response.data + if user == gov_user and status == CreditTransferStatuses.APPROVED: + self.assertEqual(response_status, 200) + else: + self.assertTrue(response_status == 403 or not data) + transfer.status = transfer_initial_status + transfer.save() diff --git a/backend/api/viewsets/credit_transfer.py b/backend/api/viewsets/credit_transfer.py index 474fe3b1e..878419e73 100644 --- a/backend/api/viewsets/credit_transfer.py +++ b/backend/api/viewsets/credit_transfer.py @@ -8,7 +8,7 @@ from api.models.credit_transfer_statuses import CreditTransferStatuses from api.permissions.credit_transfer import CreditTransferPermissions from api.serializers.credit_transfer import CreditTransferSerializer, \ - CreditTransferSaveSerializer, CreditTransferListSerializer + CreditTransferSaveSerializer, CreditTransferListSerializer, CreditTransferOrganizationBalancesSerializer from api.serializers.credit_transfer_comment import CreditTransferCommentSerializer from auditable.views import AuditableMixin from api.services.send_email import notifications_credit_transfers @@ -125,3 +125,13 @@ def delete_comment(self, request, pk): delete_comment(comment_id) return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_403_FORBIDDEN) + + @action(detail=True, methods=["GET"]) + def org_balances(self, request, pk): + transfer = self.get_queryset().filter(pk=pk).first() + if transfer is None: + return Response({}) + if request.user.is_government and transfer.status == CreditTransferStatuses.APPROVED: + serializer = CreditTransferOrganizationBalancesSerializer(transfer) + return Response(serializer.data) + return Response(status=status.HTTP_403_FORBIDDEN) diff --git a/frontend/src/app/routes/CreditTransfers.js b/frontend/src/app/routes/CreditTransfers.js index 22f3ab11f..874b0811e 100644 --- a/frontend/src/app/routes/CreditTransfers.js +++ b/frontend/src/app/routes/CreditTransfers.js @@ -7,6 +7,7 @@ const CREDIT_REQUESTS = { EDIT: `${API_BASE_PATH}/:id/edit`, UPDATE_COMMENT: `${API_BASE_PATH}/:id/update_comment`, DELETE_COMMENT: `${API_BASE_PATH}/:id/delete_comment`, + ORG_BALANCES: `${API_BASE_PATH}/:id/org_balances`, } export default CREDIT_REQUESTS diff --git a/frontend/src/app/utilities/getSupplierSummary.js b/frontend/src/app/utilities/getSupplierSummary.js index 9b0b075c2..125026907 100644 --- a/frontend/src/app/utilities/getSupplierSummary.js +++ b/frontend/src/app/utilities/getSupplierSummary.js @@ -1,17 +1,17 @@ -const getSupplierSummary = (submission) => { +const getSupplierSummary = (submission, orgBalances) => { const initiatingSupplier = { - currentBalanceA: parseFloat(submission.debitFrom.balance.A), - currentBalanceB: parseFloat(submission.debitFrom.balance.B), - newBalanceA: parseFloat(submission.debitFrom.balance.A), - newBalanceB: parseFloat(submission.debitFrom.balance.B), - supplierLabel: submission.debitFrom.name + currentBalanceA: parseFloat(orgBalances.debitFrom.balance.A), + currentBalanceB: parseFloat(orgBalances.debitFrom.balance.B), + newBalanceA: parseFloat(orgBalances.debitFrom.balance.A), + newBalanceB: parseFloat(orgBalances.debitFrom.balance.B), + supplierLabel: orgBalances.debitFrom.name } const receivingSupplier = { - currentBalanceA: parseFloat(submission.creditTo.balance.A), - currentBalanceB: parseFloat(submission.creditTo.balance.B), - newBalanceA: parseFloat(submission.creditTo.balance.A), - newBalanceB: parseFloat(submission.creditTo.balance.B), - supplierLabel: submission.creditTo.name + currentBalanceA: parseFloat(orgBalances.creditTo.balance.A), + currentBalanceB: parseFloat(orgBalances.creditTo.balance.B), + newBalanceA: parseFloat(orgBalances.creditTo.balance.A), + newBalanceB: parseFloat(orgBalances.creditTo.balance.B), + supplierLabel: orgBalances.creditTo.name } submission.creditTransferContent.forEach((item) => { if (item.creditClass.creditClass === 'A') { diff --git a/frontend/src/credits/CreditTransfersDetailsContainer.js b/frontend/src/credits/CreditTransfersDetailsContainer.js index e25c1423c..f19320539 100644 --- a/frontend/src/credits/CreditTransfersDetailsContainer.js +++ b/frontend/src/credits/CreditTransfersDetailsContainer.js @@ -23,6 +23,7 @@ const CreditTransfersDetailsContainer = (props) => { const [sufficientCredit, setSufficientCredit] = useState(true) const { id } = match.params const [submission, setSubmission] = useState({}) + const [orgBalances, setOrgBalances] = useState({}) const [loading, setLoading] = useState(true) const refreshDetails = () => { @@ -49,6 +50,14 @@ const CreditTransfersDetailsContainer = (props) => { refreshDetails() }, [id]) + useEffect(() => { + if (user.isGovernment && submission.status === 'APPROVED') { + axios.get(ROUTES_CREDIT_TRANSFERS.ORG_BALANCES.replace(':id', id)).then((response) => { + setOrgBalances(response.data) + }) + } + }, [id, user, submission]) + const handleCheckboxClick = (event) => { if (!event.target.checked) { const checked = checkboxes.filter( @@ -170,6 +179,7 @@ const CreditTransfersDetailsContainer = (props) => { submission={submission} user={user} errorMessage={errorMessage} + orgBalances={orgBalances} /> ] } diff --git a/frontend/src/credits/components/CreditTransfersDetailsPage.js b/frontend/src/credits/components/CreditTransfersDetailsPage.js index 39b853e5a..5a749d11e 100644 --- a/frontend/src/credits/components/CreditTransfersDetailsPage.js +++ b/frontend/src/credits/components/CreditTransfersDetailsPage.js @@ -32,7 +32,8 @@ const CreditTransfersDetailsPage = (props) => { sufficientCredit, submission, user, - errorMessage + errorMessage, + orgBalances } = props const { id } = useParams() const [comment, setComment] = useState('') @@ -501,9 +502,10 @@ const CreditTransfersDetailsPage = (props) => { )} - {transferRole.governmentAnalyst && ( + {transferRole.governmentAnalyst && Object.keys(orgBalances).length !== 0 && ( )} @@ -556,7 +558,8 @@ CreditTransfersDetailsPage.propTypes = { sufficientCredit: PropTypes.bool.isRequired, user: CustomPropTypes.user.isRequired, submission: PropTypes.shape().isRequired, - errorMessage: PropTypes.arrayOf(PropTypes.string) + errorMessage: PropTypes.arrayOf(PropTypes.string), + orgBalances: PropTypes.shape().isRequired } export default CreditTransfersDetailsPage diff --git a/frontend/src/credits/components/CreditTransfersDetailsSupplierTable.js b/frontend/src/credits/components/CreditTransfersDetailsSupplierTable.js index e7fbfe168..ecb8d05ec 100644 --- a/frontend/src/credits/components/CreditTransfersDetailsSupplierTable.js +++ b/frontend/src/credits/components/CreditTransfersDetailsSupplierTable.js @@ -6,7 +6,7 @@ import formatNumeric from '../../app/utilities/formatNumeric' import getSupplierSummary from '../../app/utilities/getSupplierSummary' const CreditTransfersDetailsSupplierTable = (props) => { - const { submission } = props + const { submission, orgBalances } = props const tableText = (
@@ -15,7 +15,7 @@ const CreditTransfersDetailsSupplierTable = (props) => {
) - const supplierBalanceData = getSupplierSummary(submission) + const supplierBalanceData = getSupplierSummary(submission, orgBalances) const supplierBalanceColumns = [ { Header: 'Supplier', @@ -99,6 +99,7 @@ const CreditTransfersDetailsSupplierTable = (props) => { ) } CreditTransfersDetailsSupplierTable.propTypes = { - submission: PropTypes.shape().isRequired + submission: PropTypes.shape().isRequired, + orgBalances: PropTypes.shape().isRequired } export default CreditTransfersDetailsSupplierTable