コード例 #1
0
 def archive(self, request, pk=None, **kwargs):
     """
     Override this method as we don't want to actually delete the goal,
     just disable it.
     :param instance: The goal to disable
     :return: None
     """
     goal = self.get_object()
     # If I'm an advisor or the goal is unsupervised,
     # archive the goal immediately.
     sr = SupportRequest.get_current(request, as_obj=True)
     # check helped user instead if support request is active
     user, sr_id = (sr.user, sr.id) if sr else (request.user, None)
     if not goal.account.supervised or user.is_advisor:
         check_state(Goal.State(goal.state), [
             Goal.State.ACTIVE, Goal.State.ARCHIVE_REQUESTED,
             Goal.State.INACTIVE
         ])
         Event.ARCHIVE_GOAL.log('{} {}'.format(request.method,
                                               request.path),
                                user=request.user,
                                obj=goal,
                                support_request_id=sr_id)
         # Set the state to archive requested,
         # as the call to archive() requires it.
         goal.state = Goal.State.ARCHIVE_REQUESTED.value
         goal.archive()
     else:
         # I'm a client with a supervised goal, just change the status to
         # ARCHIVE_REQUESTED, and add a notification
         check_state(Goal.State(goal.state),
                     [Goal.State.ACTIVE, Goal.State.INACTIVE])
         Event.ARCHIVE_GOAL_REQUESTED.log('{} {}'.format(
             request.method, request.path),
                                          user=request.user,
                                          obj=goal,
                                          support_request_id=sr_id)
         # Flag the goal as archive requested.
         goal.state = Goal.State.ARCHIVE_REQUESTED.value
         # TODO: Add a notification to the advisor that the goal is archive requested.
         goal.save()
     return Response(serializers.GoalSerializer(goal).data)
コード例 #2
0
    def calculate_all_portfolios(self, request, pk=None, **kwargs):
        """
        Called to calculate all the portfolio objects for 100 different risk scores for the supplied settings.
        """
        goal = self.get_object()

        check_state(Goal.State(goal.state), Goal.State.ACTIVE)

        setting_str = request.query_params.get('setting', None)
        if not setting_str:
            logger.debug(
                'setting parameter missing from calculate_all_portfolios query'
            )
            raise ValidationError(
                "Query parameter 'setting' must be specified and a valid JSON string"
            )
        try:
            setting = ujson.loads(setting_str)
        except ValueError:
            logger.debug(
                'setting parameter for calculate_all_portfolios query not valid json'
            )
            raise ValidationError(
                "Query parameter 'setting' must be a valid json string")

        # Create the settings from the dict
        serializer = serializers.GoalSettingStatelessSerializer(data=setting)
        serializer.is_valid(raise_exception=True)
        settings = serializer.create_stateless(serializer.validated_data, goal)

        # Calculate the portfolio
        try:
            data_provider = DataProviderDjango()
            execution_provider = ExecutionProviderDjango()
            data = [
                self.build_portfolio_data(item[1], item[0]) for item in
                calculate_portfolios(settings=settings,
                                     data_provider=data_provider,
                                     execution_provider=execution_provider)
            ]
            return Response(data)
        except Unsatisfiable as e:
            rdata = {'reason': "No portfolio could be found: {}".format(e)}

            if e.req_funds is not None:
                rdata['req_funds'] = e.req_funds

            return Response({'error': rdata},
                            status=status.HTTP_400_BAD_REQUEST)
コード例 #3
0
    def revert_selected(self, request, pk=None, **kwargs):
        """
        Called to revert the current selected-settings to the approved-settings
        Returns a validation error if there is no approved-settings.
        """
        goal = self.get_object()
        check_state(Goal.State(goal.state), Goal.State.ACTIVE)
        if not goal.approved_settings:
            raise ValidationError("No settings have yet been approved for "
                                  "this Goal, cannot revert to last approved.")
        sr_id = SupportRequest.get_current(request)
        Event.REVERT_SELECTED_SETTINGS.log('{} {}'.format(
            request.method, request.path),
                                           user=request.user,
                                           obj=goal,
                                           support_request_id=sr_id)
        goal.revert_selected()
        serializer = serializers.GoalSettingSerializer(goal.selected_settings)

        return Response(serializer.data)
コード例 #4
0
    def calculate_portfolio(self, request, pk=None, **kwargs):
        """
        Called to calculate a portfolio object for a set of supplied settings.
        """
        goal = self.get_object()

        check_state(Goal.State(goal.state),
                    [Goal.State.ACTIVE, Goal.State.INACTIVE])

        setting_str = request.query_params.get('setting', None)
        if not setting_str:
            raise ValidationError(
                "Query parameter 'setting' must be specified and a valid JSON string"
            )
        try:
            setting = ujson.loads(setting_str)
        except ValueError:
            raise ValidationError(
                "Query parameter 'setting' must be a valid json string")

        # Create the settings object from the dict
        serializer = serializers.GoalSettingStatelessSerializer(data=setting)
        serializer.is_valid(raise_exception=True)
        settings = serializer.create_stateless(serializer.validated_data, goal)

        try:
            data = self.build_portfolio_data(
                calculate_portfolio(
                    settings=settings,
                    data_provider=DataProviderDjango(),
                    execution_provider=ExecutionProviderDjango()))
            return Response(data)
        except Unsatisfiable as e:
            rdata = {'reason': "No portfolio could be found: {}".format(e)}
            if e.req_funds is not None:
                rdata['req_funds'] = e.req_funds

            return Response({'error': rdata},
                            status=status.HTTP_400_BAD_REQUEST)
コード例 #5
0
    def approve_selected(self, request, pk=None, **kwargs):
        """
        Called to make the currently selected settings approved by the advisor,
        ready to be activated next time the account is processed (rebalance).
        """
        sr = SupportRequest.get_current(request, as_obj=True)
        # check helped user instead if support request is active
        user, sr_id = (sr.user, sr.id) if sr else (request.user, None)
        if not user.is_advisor:
            raise PermissionDenied('Only an advisor can approve selections.')

        goal = self.get_object()
        check_state(Goal.State(goal.state),
                    [Goal.State.ACTIVE, Goal.State.INACTIVE])
        Event.APPROVE_SELECTED_SETTINGS.log('{} {}'.format(
            request.method, request.path),
                                            user=request.user,
                                            obj=goal,
                                            support_request_id=sr_id)
        goal.approve_selected()

        serializer = serializers.GoalSettingSerializer(goal.approved_settings)

        return Response(serializer.data)
コード例 #6
0
    def withdraw(self, request, pk=None, **kwargs):
        goal = self.get_object()
        check_state(Goal.State(goal.state), Goal.State.ACTIVE)

        client = goal.account.primary_owner
        site = get_current_site(request)
        target_user = client.user
        sr_id = SupportRequest.get_current(request)

        if site.config().plaid_enabled:
            if not hasattr(target_user, 'invitation'):
                logger.error('Django user missing invitation')
                raise ValidationError(
                    'Client missing invitation, needed for withdrawals')
            else:
                if not hasattr(target_user.invitation, 'photo_verification'):
                    logger.error(
                        'Django user missing invitation.photo_verification')
                    raise ValidationError(
                        'Client missing invitation.photo_verification, needed for withdrawals'
                    )

        serializer = serializers.TransactionCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # Make sure the total amount for the goal is larger than the pending withdrawal amount.
        if goal.current_balance + 0.0000001 < serializer.validated_data[
                'amount']:
            emsg = "Goal's current balance: {} is less than the desired withdrawal amount: {}"
            raise SystemConstraintError(
                emsg.format(goal.current_balance,
                            serializer.validated_data['amount']))

        if site.config().plaid_enabled:
            # make sure user has connected a plaid_user
            plaid_user = getattr(target_user, 'plaid_user', False)
            if not plaid_user:
                logger.error(
                    'Withdrawal attempted by %s but no plaid_user found' %
                    target_user)
                raise ValidationError('Client has no plaid_user linked')

            if not serializer.validated_data.get('account_id', False):
                logger.error(
                    'No plaid account_id sent with withdrawal request')
                raise ValidationError(
                    'No plaid account_id sent with withdrawal request')

        transaction = serializer.save(from_goal=goal,
                                      reason=Transaction.REASON_WITHDRAWAL)

        if site.config().plaid_enabled:
            # execute transaction through stripe
            logger.info('Executing valid withdrawal request from %s for %s' %
                        (target_user, serializer.validated_data['amount']))
            stripe_token = get_stripe_account_token(
                target_user, serializer.validated_data['account_id'])
            try:
                transfer = execute_withdrawal(
                    target_user, serializer.validated_data['amount'],
                    stripe_token)
                if not transfer:
                    logger.error(
                        'Failed to execute withdrawal, marking Transaction %s failed'
                        % transaction)
                    transaction.status = Transaction.STATUS_FAILED
                    transaction.save()
                    return Response('Failed to charge account',
                                    status=status.HTTP_400_BAD_REQUEST)
                transaction.stripe_id = transfer.id
                transaction.save()
            except Exception as e:
                logger.error(
                    'Failed to execute withdrawal, marking Transaction %s failed'
                    % transaction)
                logger.error(e)
                transaction.status = Transaction.STATUS_FAILED
                transaction.save()
                return Response('Failed to charge account',
                                status=status.HTTP_400_BAD_REQUEST)
        else:
            # manual transfer, goal cash balance updated directly
            transaction.execute()

        Event.GOAL_WITHDRAWAL.log('{} {}'.format(request.method, request.path),
                                  request.data,
                                  user=request.user,
                                  obj=goal,
                                  support_request_id=sr_id)

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data,
                        status=status.HTTP_201_CREATED,
                        headers=headers)
コード例 #7
0
    def selected_settings(self, request, pk=None, **kwargs):
        goal = self.get_object()

        if request.method == 'GET':
            serializer = serializers.GoalSettingSerializer(
                goal.selected_settings)
            return Response(serializer.data)

        sr_id = SupportRequest.get_current(request)
        data = request.data
        roa = None
        if request.method == 'POST':
            with transaction.atomic(
            ):  # So both the log and change get committed.
                check_state(Goal.State(goal.state),
                            [Goal.State.ACTIVE, Goal.State.INACTIVE])
                # If this field is specified and it is an update from an advisor, we create a record of advice.
                if 'record_of_advice' in request.data:
                    roa_data = data.pop('record_of_advice')
                    roa_ser = serializers.RecordOfAdviceSerializer(
                        data=roa_data)
                    roa_ser.initial_data['goal'] = goal.id
                    roa_ser.is_valid(raise_exception=True)
                    roa = roa_ser.save()

                serializer = serializers.GoalSettingWritableSerializer(
                    data=data, context={'request': request})
                serializer.is_valid(raise_exception=True)
                event = Event.SET_SELECTED_SETTINGS.log(
                    '{} {}'.format(self.request.method, self.request.path),
                    request.data,
                    user=request.user,
                    obj=goal,
                    support_request_id=sr_id)

                settings = serializer.save(goal=goal)

            if roa:
                roa.send_roa_generated_email()
            # We use the read-only serializer to send the settings object, not the update serializer.
            serializer = serializers.GoalSettingSerializer(settings)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data,
                            status=status.HTTP_200_OK,
                            headers=headers)

        elif request.method == 'PUT':
            with transaction.atomic(
            ):  # So both the log and change get committed.
                check_state(Goal.State(goal.state),
                            [Goal.State.ACTIVE, Goal.State.INACTIVE])
                # If this field is specified and it is an update from an advisor, we create a record of advice.
                if 'record_of_advice' in request.data:
                    roa_data = data.pop('record_of_advice')
                    roa_ser = serializers.RecordOfAdviceSerializer(
                        data=roa_data, context={'request': request})
                    roa_ser.initial_data['goal'] = goal.id
                    roa_ser.is_valid(raise_exception=True)
                    roa = roa_ser.save()

                settings = goal.selected_settings
                event = Event.UPDATE_SELECTED_SETTINGS.log(
                    '{} {}'.format(self.request.method, self.request.path),
                    request.data,
                    user=request.user,
                    obj=goal,
                    support_request_id=sr_id)
                serializer = serializers.GoalSettingWritableSerializer(
                    settings, data=data, partial=True)
                serializer.is_valid(raise_exception=True)
                settings = serializer.save(goal=goal)

            if roa:
                roa.send_roa_generated_email()
            # We use the read-only serializer to send the settings object, not the update serializer.
            serializer = serializers.GoalSettingSerializer(settings)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data,
                            status=status.HTTP_200_OK,
                            headers=headers)