Repository: akvo/akvo-rsr

Issue:
Amount needs funding
## Test plan

GIVEN a project
WHEN it has less than ($/€) 1 funds needed (= total budget - total funding by funding partners)
THEN the funds needed amount is set to ($/€) 0

his is accomplished by using a management command:
`manage.py funds_needed [option] value`

GIVEN the funds_needed management command
WHEN there's no value parameter for the command to run
THEN an error is shown

GIVEN the funds_needed management command
WHEN only a value and no option is provided
THEN all projects with funds needed less than the indicated value should be printed to console

GIVEN the funds_needed management command
WHEN the `--zero` option is provided
THEN all projects with funds needed less than the indicated value should set their funds_needed to 0

GIVEN the funds_needed management command
WHEN the `--complete` option is provided
THEN all projects with funds needed less than the indicated value should set their status to 'Complete'
## Issue description

From the product meeting, it became clear that we have to look at 2 things regarding funding:
- [x] There are quite a few projects with $1 funding needed, these need to be set to completed.
- [x] How much funding is needed? This is only visible on the project funding page and on the second page of donations, but it should be apparent on the project list already, if relevant.



Discussion:
It's worth checking out the thoughts and comments in these 2 issues:
https://github.com/akvo/akvo-rsr/issues/440
https://github.com/akvo/akvo-rsr/issues/120

To add: http://rsr.akvo.org/project/1588/finance/ This project has received money from funding (via admin) and via donation (via PayPall or iDeal). Due to the fee you pay to pay via our donation button, the project budget leftover is now 18 cents. This is also the problem with a lot of the Mars projects, like this one: http://rsr.akvo.org/project/739/

Not sure what to do here... Anyway here's a list of projects with less than 1 funding needed:

``` text
id   |funds_needed |
-----|-------------|
42   |0.40         |
43   |0.38         |
49   |0.40         |
54   |0.05         |
56   |0.14         |
61   |0.05         |
74   |0.42         |
75   |0.35         |
94   |0.18         |
107  |0.13         |
108  |0.23         |
120  |0.10         |
121  |0.13         |
132  |0.25         |
135  |0.18         |
138  |0.36         |
141  |0.21         |
149  |0.18         |
174  |0.18         |
179  |0.25         |
187  |0.18         |
204  |0.15         |
210  |0.18         |
211  |0.18         |
245  |0.06         |
247  |0.18         |
248  |0.18         |
249  |0.08         |
250  |0.18         |
251  |0.18         |
252  |0.18         |
253  |0.18         |
256  |0.18         |
257  |0.18         |
258  |0.18         |
263  |0.18         |
264  |0.18         |
265  |0.36         |
268  |0.03         |
273  |0.18         |
274  |0.46         |
297  |0.36         |
298  |0.25         |
304  |0.08         |
332  |0.18         |
348  |0.36         |
397  |0.12         |
450  |0.45         |
453  |0.30         |
483  |0.29         |
511  |0.01         |
512  |0.01         |
513  |0.02         |
538  |0.01         |
540  |0.01         |
591  |0.44         |
592  |0.38         |
594  |0.14         |
597  |0.19         |
610  |0.27         |
612  |0.18         |
660  |0.33         |
690  |0.01         |
695  |0.01         |
1340 |0.01         |
1365 |0.25         |
1588 |0.18         |
1897 |0.11         |
2203 |0.01         |
2228 |0.49         |
2330 |0.08         |
3187 |0.01         |
```

Are these budget leftovers due to the fee a donor pays by using PayPall? I think this is a problem, becuase then a budget can never be fully funded if it's by online donations. 
If I look at the projects in the list above, I think you can't see these less than 1 euro leftovers anymore, since we removed decimals, e.g. http://rsr.akvo.org/en/project/107/finance/


Resolving patch:
diff --git a/akvo/rsr/management/commands/funds_needed.py b/akvo/rsr/management/commands/funds_needed.py
new file mode 100644
--- /dev/null
+++ b/akvo/rsr/management/commands/funds_needed.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+
+# Akvo Reporting is covered by the GNU Affero General Public License.
+# See more details in the license.txt file located at the root folder of the Akvo RSR module.
+# For additional details on the GNU license please see < http://www.gnu.org/licenses/agpl.html >.
+
+from decimal import Decimal, InvalidOperation
+from django.core.management.base import BaseCommand
+from optparse import make_option
+from ...models import Project
+
+
+class Command(BaseCommand):
+    args = '<value>'
+    help = 'Checks all projects with a funding between 0 and a specified value'
+
+    option_list = BaseCommand.option_list + (
+        make_option(
+            '--zero',
+            action='store_true',
+            dest='zero',
+            default=False,
+            help='Set needs funding to 0, instead of only printing results'),
+        make_option(
+            '--complete',
+            action='store_true',
+            dest='complete',
+            default=False,
+            help='Set projects to the \'Complete\' status'),
+    )
+
+    def __init__(self, *args, **kwargs):
+        self.value = None
+        self.projects = None
+        super(Command, self).__init__(*args, **kwargs)
+
+    def get_projects(self):
+        self.projects = Project.objects.filter(funds_needed__gt=0., funds_needed__lt=self.value)
+
+    def print_projects(self):
+        self.stdout.write('')
+        self.stdout.write('Found {0} project{1}...'.format(
+            str(self.projects.count()),
+            's' if not self.projects.count() == 1 else ''))
+
+        if self.projects.count() > 0:
+            self.stdout.write('')
+            self.stdout.write(' id   | funds needed |')
+            self.stdout.write('------|--------------|')
+            for p in self.projects.order_by('id'):
+                self.stdout.write('{pk:{fill}6}|{funds:{fill}14}|'.format(
+                    pk=p.pk, funds=p.funds_needed, fill=' '))
+            self.stdout.write('------|--------------|')
+
+    def handle(self, *args, **options):
+        """
+        Walk through all projects and find projects where the needs funding is in between 0 and the
+        provided decimal. Print these projects, and set the needs_funding field to 0 if the --do
+        option is supplied.
+        """
+        error = ''
+        if len(args) != 1:
+            error = 'Please specify exactly 1 value'
+        else:
+            try:
+                self.value = Decimal(args[0])
+            except InvalidOperation:
+                error = 'Please specify a decimal, using a period to denote the decimals'
+
+        if error:
+            self.stdout.write(error)
+            self.stdout.write('')
+            self.stdout.write('Usage: /var/akvo/rsr/code/manage.py needs_funding [options] <value>')
+            self.stdout.write('')
+            return
+
+        self.get_projects()
+        self.print_projects()
+
+        # Set funds needed to 0 for the retrieved projects
+        if self.projects.count() > 0 and options['zero']:
+            for p in self.projects:
+                p.funds_needed = 0.
+                p.save()
+
+            self.stdout.write('')
+            self.stdout.write('Succesfully set funds needed to 0 for {0} project{1}'.format(
+                str(self.projects.count()),
+                's' if not self.projects.count() == 1 else ''))
+
+        # Set projects to 'Complete' for the retrieved projects
+        if self.projects.count() > 0 and options['complete']:
+            for p in self.projects:
+                p.status = Project.STATUS_COMPLETE
+                p.save()
+
+            self.stdout.write('')
+            self.stdout.write('Succesfully set status to \'Complete\' for {0} project{1}'.format(
+                str(self.projects.count()),
+                's' if not self.projects.count() == 1 else ''))
+
+        self.stdout.write('')
+        self.stdout.write('Done!')
+        self.stdout.write('')
diff --git a/akvo/rsr/models/project.py b/akvo/rsr/models/project.py
--- a/akvo/rsr/models/project.py
+++ b/akvo/rsr/models/project.py
@@ -527,10 +527,12 @@ def update_funds(self):
         self.save()
 
     def get_funds_needed(self):
-        """ How much more is needed to fulfill the project's budget needs
-            Note that this may be a small negative if there's been an overshooting donation
         """
-        return self.get_budget() - self.get_funds()
+        How much more is needed to fulfill the project's budget needs. In case of a negative value
+        or a value less than 1, the value is set to 0.
+        """
+        funds_needed = self.get_budget() - self.get_funds()
+        return funds_needed if funds_needed >= 1 else 0.0
 
     def update_funds_needed(self):
         "Update de-normalized field"


Test patch: