Пример #1
0
    def get(self, request):
        """
        ---
        # Swagger

        response_serializer: serializers.UserSerializer
        """
        user = SupportRequest.target_user(request)
        if user.is_support_staff:
            sr = SupportRequest.get_current(self.request, as_obj=True)
            user = sr.user
        data = self.serializer_class(user).data

        if user.is_advisor:
            data['advisor'] = AdvisorSerializer(user.advisor).data
        elif user.is_client:
            data['client'] = ClientFieldSerializer(user.client).data
        elif user.is_supervisor:
            data['supervisor'] = SupervisorSerializer(user.supervisor).data
        elif user.is_authorised_representative:
            data['authorised_representative'] = AuthorisedRepresentativeSerializer(user.authorised_representative).data
        elif hasattr(user, 'invitation'):
            if user.invitation and user.invitation.status == EmailInvite.STATUS_ACCEPTED:
                data['invitation'] = InvitationSerializer(instance=user.invitation).data
            else:
                raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.")
        elif hasattr(user, 'firm_invitation'):
            if user.firm_invitation and user.firm_invitation.status == FirmEmailInvite.STATUS_ACCEPTED:
                data['invitation'] = FirmInvitationSerializer(instance=user.firm_invitation).data
            else:
                raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.")
        else:
            raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.")
        return Response(data)
Пример #2
0
    def get(self, request):
        """
        ---
        # Swagger

        response_serializer: serializers.UserSerializer
        """
        user = SupportRequest.target_user(request)
        if user.is_support_staff:
            sr = SupportRequest.get_current(self.request, as_obj=True)
            user = sr.user
        data = self.serializer_class(user).data

        if user.is_advisor:
            role = 'advisor'
            data['advisor'] = AdvisorSerializer(user.advisor).data
        elif user.is_client:
            role = 'client'
            data['client'] = ClientFieldSerializer(user.client).data
        elif user.invitation and user.invitation.status == EmailInvite.STATUS_ACCEPTED:
            role = 'client'
            data['invitation'] = InvitationSerializer(instance=user.invitation).data
        else:
            raise PermissionDenied("User is not in the client or advisor groups, or is not a new user accepting an invitation.")
        data.update({'role': role})
        return Response(data)
Пример #3
0
    def awaiting_deposit(self, request, pk=None, deposit_pk=None, **kwargs):
        """
        Gets or updates awaiting deposit by id.
        """
        goal = self.get_object()

        sr_id = SupportRequest.get_current(request)
        try:
            transaction = Transaction.objects.get(
                Q(pk=deposit_pk),
                Q(status=Transaction.STATUS_AWAITING_APPROVAL),
                Q(to_goal=goal))
        except ObjectDoesNotExist:
            raise ValidationError(
                'Can not find specified awaiting one-time deposit')

        if request.method == 'GET':
            serializer = serializers.TransactionSerializer(transaction)
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data,
                            status=status.HTTP_200_OK,
                            headers=headers)
        elif request.method == 'PUT':
            client = goal.account.primary_owner
            site = get_current_site(request)

            serializer = serializers.TransactionSerializer(transaction,
                                                           data=request.data)
            serializer.is_valid(raise_exception=True)
            if site.config().plaid_enabled:
                # make sure user has connected a plaid_user
                target_user = client.user
                plaid_user = getattr(target_user, 'plaid_user', False)
                if not plaid_user:
                    logger.error(
                        'Deposit attempted by %s but no plaid_user found' %
                        request.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 deposit request')
                    raise ValidationError(
                        'No plaid account_id sent with deposit request')

            transaction = serializer.save()
            transaction.save()

            # TODO: Check whether other event log type is required for deposit update
            Event.GOAL_DEPOSIT.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_200_OK,
                            headers=headers)
Пример #4
0
 def update(self, goal, validated_data):
     # Override the update method so we can make sure that only advisors can
     # update state, and only if the last state was ARCHIVE_REQUESTED
     request = self.context.get('request')
     new_state = validated_data.get('state', None)
     if new_state is not None and new_state != goal.state:
         if new_state != Goal.State.ACTIVE.value:
             raise PermissionDenied("The only state transition allowed is "
                                    "to {}".format(Goal.State.ACTIVE))
         if goal.state != Goal.State.ARCHIVE_REQUESTED.value:
             raise PermissionDenied(
                 "The only state transition allowed is from {}".format(
                     Goal.State.ARCHIVE_REQUESTED))
         sr = SupportRequest.get_current(request, as_obj=True)
         user, sr_id = (sr.user, sr.id) if sr else (request.user, None)
         # check helped user instead if support request is active
         if not user.is_advisor:
             raise PermissionDenied("Only an advisor can reactivate a goal")
         Event.REACTIVATE_GOAL.log('{} {}'.format(request.method,
                                                  request.path),
                                   user=request.user,
                                   obj=goal,
                                   support_request_id=sr_id)
     # Finally, if we pass the validation, allow the update
     return super(GoalUpdateSerializer, self).update(goal, validated_data)
Пример #5
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)

        with transaction.atomic():  # So both the log and change get committed.
            sr_id = SupportRequest.get_current(request)
            if request.method == 'POST':
                check_state(Goal.State(goal.state), Goal.State.ACTIVE)
                serializer = serializers.GoalSettingWritableSerializer(
                    data=request.data)
                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)
                # Write any event memo for the event. All the details are wrapped by the serializer.
                serializer.write_memo(event)
                settings = serializer.save(goal=goal)
                # 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':
                check_state(Goal.State(goal.state), Goal.State.ACTIVE)
                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=request.data, partial=True)
                serializer.is_valid(raise_exception=True)
                # Write any event memo for the event. All the details are wrapped by the serializer.
                serializer.write_memo(event)
                settings = serializer.save(goal=goal)
                # 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)
Пример #6
0
    def deposit(self, request, pk=None, **kwargs):
        goal = self.get_object()

        check_state(Goal.State(goal.state), Goal.State.ACTIVE)
        sr_id = SupportRequest.get_current(request)
        Event.GOAL_DEPOSIT.log('{} {}'.format(request.method, request.path),
                               request.data,
                               user=request.user,
                               obj=goal,
                               support_request_id=sr_id)
        serializer = serializers.TransactionCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save(to_goal=goal, reason=Transaction.REASON_DEPOSIT)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data,
                        status=status.HTTP_201_CREATED,
                        headers=headers)
Пример #7
0
    def create(self, validated_data):
        request = self.context.get('request')
        sr = SupportRequest.get_current(request, as_obj=True)
        user, sr_id = (sr.user, sr.id) if sr else (request.user, None)
        roa = super(RecordOfAdviceSerializer, self).create(validated_data)
        Event.ROA_GENERATED.log('Record of Advice Generated',
                                user=request.user,
                                obj=roa,
                                support_request_id=sr_id)

        goal = validated_data['goal']
        client = goal.account.primary_owner
        Notify.ADVISOR_CREATE_ROA.send(actor=user,
                                       recipient=client.user,
                                       action_object=roa,
                                       target=goal)

        return roa
Пример #8
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)
Пример #9
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)
Пример #10
0
    def render(self, data, media_type=None, renderer_context=None):
        """
        NB. be sure that settings.REST_FRAMEWORK contains:
        'EXCEPTION_HANDLER': '...api_exception_handler',
        """
        logger = logging.getLogger(__name__)
        wrapper = {
            'version': '2',
            'data': {},
            'meta': {},
        }

        # move error to the root level
        if hasattr(data, 'get') and data.get('error'):
            wrapper['error'] = data['error']
            del data['error']

        if data is not None:
            wrapper['data'] = data

        try:
            response = renderer_context['response']
            request = renderer_context['request']
            if 200 <= response.status_code < 400:
                meta = {}
                session_expire = SessionExpire(request)
                meta['session_expires_on'] = session_expire.expire_time()

                sr = SupportRequest.get_current(request, as_obj=True)
                if sr:
                    meta['support_request'] = {
                        'ticket': sr.ticket,
                        'user': UserSerializer(instance=sr.user).data,
                    }

                wrapper['meta'] = meta
        except (TypeError, KeyError) as e:
            logger.error("Missing parameteres (%s)", e)

        return super(ApiRenderer, self).render(wrapper, media_type,
                                               renderer_context)
Пример #11
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)
        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)
Пример #12
0
    def withdraw(self, request, pk=None, **kwargs):
        goal = self.get_object()

        check_state(Goal.State(goal.state), Goal.State.ACTIVE)
        sr_id = SupportRequest.get_current(request)
        Event.GOAL_WITHDRAWAL.log('{} {}'.format(request.method, request.path),
                                  request.data,
                                  user=request.user,
                                  obj=goal,
                                  support_request_id=sr_id)
        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']))
        serializer.save(from_goal=goal, reason=Transaction.REASON_WITHDRAWAL)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data,
                        status=status.HTTP_201_CREATED,
                        headers=headers)
Пример #13
0
    def put(self, request):
        """
        ---
        # Swagger

        request_serializer: serializers.UserUpdateSerializer
        response_serializer: serializers.UserSerializer
        """
        user = SupportRequest.target_user(request)
        if user.is_support_staff:
            sr = SupportRequest.get_current(self.request, as_obj=True)
            user = sr.user
        serializer = serializers.UserUpdateSerializer(user,
                                                      data=request.data,
                                                      partial=True,
                                                      context={
                                                          'request': request,
                                                      })

        serializer.is_valid(raise_exception=True)

        user = serializer.save()

        data = self.serializer_class(user).data
        if user.is_advisor:
            role = 'advisor'
            data['advisor'] = AdvisorSerializer(user.advisor).data
        elif user.is_client:
            role = 'client'
            # If the user wants to update client details, they do it through the specific client endpoint.
            data['client'] = ClientFieldSerializer(user.client).data
        else:
            raise PermissionDenied("User is not in the client or "
                                   "advisor groups.")
        data.update({'role': role})
        return Response(data)
Пример #14
0
def is_support_staff(request):
    try:
        return SupportRequest.get_current(request) and \
               request.user.is_support_staff
    except AttributeError:  # anonymous users don't have `is_support_staff`
        pass
Пример #15
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)
Пример #16
0
    def deposit(self, request, pk=None, **kwargs):
        goal = self.get_object()

        sr_id = SupportRequest.get_current(request)
        Event.GOAL_DEPOSIT.log('{} {}'.format(request.method, request.path),
                               request.data,
                               user=request.user,
                               obj=goal,
                               support_request_id=sr_id)
        serializer = serializers.TransactionCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        client = goal.account.primary_owner
        site = get_current_site(request)
        if site.config().plaid_enabled:
            # make sure user has connected a plaid_user
            target_user = client.user
            plaid_user = getattr(target_user, 'plaid_user', False)
            if not plaid_user:
                logger.error(
                    'Deposit 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 deposit request')
                raise ValidationError(
                    'No plaid account_id sent with deposit request')

        transaction = serializer.save(to_goal=goal,
                                      reason=Transaction.REASON_DEPOSIT)

        # check if goal is active
        if transaction.to_goal.state == Goal.State.ACTIVE.value and \
           goal.approved_settings == goal.selected_settings:
            if site.config().plaid_enabled:
                # execute transaction through stripe if active
                logger.info('Executing valid deposit request from %s for %s' %
                            (target_user, serializer.validated_data['amount']))
                stripe_token = get_stripe_account_token(
                    target_user, serializer.validated_data['account_id'])
                if stripe_token is None:
                    logger.error('Failed to retrieve stripe_token for %s' %
                                 target_user)
                    raise ValidationError('Failed to retrieve stripe_token')
                charge = execute_charge(serializer.validated_data['amount'],
                                        stripe_token)
                transaction.stripe_id = charge.id
                transaction.status = Transaction.STATUS_PENDING
                transaction.save()
            else:
                # manual transfer, goal cash balance updated directly
                transaction.execute()

        else:
            logger.info(
                'Saving transaction %s with status awaiting approval, to_goal %s is not active'
                % (transaction, goal))
            transaction.status = Transaction.STATUS_AWAITING_APPROVAL
            transaction.save()

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data,
                        status=status.HTTP_201_CREATED,
                        headers=headers)
Пример #17
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)