Exemplo n.º 1
0
def create_payout_finished_project(sender, instance, created, **kwargs):
    """
    Create or update Payout for finished projects.
    Project finish when deadline is hit or when it's changed manually in admin.
    """
    from localflavor.generic.validators import IBANValidator

    PROJECT_PAYOUT_MODEL = get_project_payout_model()

    project = instance

    if project.status.slug in ['done-complete', 'done-incomplete'] \
            and project.amount_asked:

        PROJECT_PAYOUT_MODEL = get_project_payout_model()
        next_date = PROJECT_PAYOUT_MODEL.get_next_planned_date()

        try:
            # Update existing Payout
            payout = PROJECT_PAYOUT_MODEL.objects.get(project=project, protected=False)

            if payout.status == StatusDefinition.NEW:
                # Update planned payout date for new Payouts
                payout.calculate_amounts()
                payout.planned = next_date
                payout.save()

        except PROJECT_PAYOUT_MODEL.DoesNotExist:

            if project.campaign_started:
                # Create new Payout
                payout = PROJECT_PAYOUT_MODEL(
                    planned=next_date,
                    project=project
                )

                # Calculate amounts
                payout.calculate_amounts()

                if project.is_closed:
                    payout.status = StatusDefinition.SETTLED

                # Set payment details
                try:
                    IBANValidator()(project.account_number)
                    payout.receiver_account_iban = project.account_number
                except ValidationError as e:
                    logger.info(
                        "IBAN error payout {0}, project: {1}: {2}".format(
                            payout.id, project.id, e.message))

                payout.receiver_account_bic = project.account_bic
                payout.receiver_account_number = project.account_number
                payout.receiver_account_name = project.account_holder_name
                payout.receiver_account_city = project.account_holder_city
                payout.receiver_account_country = project.account_bank_country

                payout.save()
Exemplo n.º 2
0
def create_payout_finished_project(sender, instance, created, **kwargs):
    """
    Create or update Payout for finished projects.
    Project finish when deadline is hit or when it's changed manually in admin.
    """
    from localflavor.generic.validators import IBANValidator

    project = instance
    now = timezone.now()

    if (project.is_realised or project.is_closed) and project.amount_asked:

        with LocalTenant():

            if now.day <= 15:
                next_date = timezone.datetime(now.year, now.month, 15)
            else:
                next_date = timezone.datetime(now.year, now.month, 1) + timedelta(days=20)

            PROJECT_PAYOUT_MODEL = get_project_payout_model()

            try:
                # Update existing Payout
                payout = PROJECT_PAYOUT_MODEL.objects.get(project=project)

                if payout.status == StatusDefinition.NEW:
                    # Update planned payout date for new Payouts
                    payout.calculate_amounts()
                    payout.planned = next_date
                    payout.save()

            except PROJECT_PAYOUT_MODEL.DoesNotExist:

                if project.campaign_started:
                    # Create new Payout
                    payout = PROJECT_PAYOUT_MODEL(
                        planned=next_date,
                        project=project
                    )

                    # Calculate amounts
                    payout.calculate_amounts()

                    if project.is_closed:
                        payout.status = StatusDefinition.SETTLED

                    # Set payment details
                    try:
                        IBANValidator()(project.account_number)
                        payout.receiver_account_iban = project.account_number
                    except ValidationError as e:
                        logger.info("IBAN error for payout id {0} and project id: {1}: {2}".format(payout.id, project.id, e.message))

                    payout.receiver_account_bic = project.account_bic
                    payout.receiver_account_number = project.account_number
                    payout.receiver_account_name = project.account_holder_name
                    payout.receiver_account_city = project.account_holder_city
                    payout.receiver_account_country = project.account_bank_country

                    payout.save()
Exemplo n.º 3
0
def create_payout_finished_project(sender, instance, created, **kwargs):
    """
    Create or update Payout for finished projects.
    Project finish when deadline is hit or when it's changed manually in admin.
    """

    project = instance
    now = timezone.now()

    if project.is_realised and project.amount_asked:

        if now.day <= 15:
            next_date = timezone.datetime(now.year, now.month, 15)
        else:
            next_date = timezone.datetime(now.year, now.month, 1) + timedelta(days=20)

        PROJECT_PAYOUT_MODEL = get_project_payout_model()

        try:
            # Update existing Payout
            payout = PROJECT_PAYOUT_MODEL.objects.get(project=project)

            if payout.status == StatusDefinition.NEW:
                # Update planned payout date for new Payouts
                payout.calculate_amounts()
                payout.planned = next_date
                payout.save()

        except PROJECT_PAYOUT_MODEL.DoesNotExist:

            # Create new Payout
            payout = PROJECT_PAYOUT_MODEL(
                planned=next_date,
                project=project
            )

            # Calculate amounts
            payout.calculate_amounts()

            # Set payment details
            organization = project.organization
            payout.receiver_account_bic = organization.account_bic
            payout.receiver_account_iban = organization.account_iban
            payout.receiver_account_number = organization.account_number
            payout.receiver_account_name = organization.account_holder_name
            payout.receiver_account_city = organization.account_holder_city
            payout.receiver_account_country = organization.account_bank_country

            # Generate invoice reference, saves twice
            payout.update_invoice_reference(auto_save=True)
Exemplo n.º 4
0
    def get_affected_records(self):
        """
        Retrieve the donations and payouts that will be affected by taking a cut.
        """
        ProjectPayout = get_project_payout_model()
        affected = {}

        donations = self.object.local_payment.order_payment.order.donations.select_related(
            "project", "project__projectpayout"
        )

        for donation in donations:
            payouts = donation.project.projectpayout_set.all()
            in_progress_payout, updateable_payout, new_payout, new_status = None, None, None, False  # defaults

            if not payouts.exists():
                new_status = donation.order.get_status_mapping(self.object.payment_type)
            else:
                updateable_payout = payouts.filter(status=StatusDefinition.NEW, protected=False).first()
                if updateable_payout is not None:
                    new_status = donation.order.get_status_mapping(self.object.payment_type)
                else:
                    # figure out if we can take cut from an unprocessed payout
                    in_progress_payout = payouts.filter(status=StatusDefinition.IN_PROGRESS).first()
                    if in_progress_payout is None:
                        new_payout = ProjectPayout(
                            project=donation.project,
                            protected=True,
                            amount_raised=0,
                            amount_payable=0,
                            organization_fee=-donation.amount,
                            planned=ProjectPayout.get_next_planned_date(),
                            description_line1="Taking cut from organization fees",
                            description_line2="from failed payment %d" % self.object.local_payment.pk,
                        )

            affected[donation] = {
                "new_status": new_status,
                "updateable_payout": updateable_payout,
                "in_progress_payout": in_progress_payout,
                "new_payout": new_payout,
            }

        return affected
Exemplo n.º 5
0
    def _get_organization_fee(self):
        """
        Calculate and return the organization fee for Payouts within this
        OrganizationPayout's period, including VAT.

        Note: this should *only* be called internally.
        """
        PROJECT_PAYOUT_MODEL = get_project_payout_model()
        # Get Payouts
        payouts = PROJECT_PAYOUT_MODEL.objects.filter(
            completed__gte=self.start_date,
            completed__lte=self.end_date
        )

        # Aggregate value
        aggregate = payouts.aggregate(models.Sum('organization_fee'))

        # Return aggregated value or 0.00
        fee = aggregate.get(
            'organization_fee__sum', decimal.Decimal('0.00')
        ) or decimal.Decimal('0.00')

        return fee
Exemplo n.º 6
0
    def get_queryset(self):
        """
        Ensure that only the complex 'take cut' RDP's are considered.

        Exclude RDP's that only have payouts that are new and unprotected, as
        these will automatically be updated by re-calculating the payout.
        """

        # find RDP's that have project payouts and a 'bad' status
        queryset = RemoteDocdataPayment.objects.filter(
            status=RemoteDocdataPayment.IntegrityStatus.InconsistentChargeback,
            local_payment__order_payment__order__donations__project__projectpayout__isnull=False,
        )

        payout_ids = list(
            queryset.values_list(
                "local_payment__order_payment__order__donations__project__projectpayout", flat=True
            ).distinct()
        )

        if not payout_ids:
            messages.warn(_("There were no payouts for this payment - aborting."))

        ProjectPayout = get_project_payout_model()
        payouts_to_ignore = list(
            ProjectPayout.objects.filter(id__in=payout_ids)
            .exclude(status=StatusDefinition.NEW, protected=False)
            .values_list("id", flat=True)
        )

        for pk in payouts_to_ignore:
            payout_ids.remove(pk)

        return queryset.filter(
            local_payment__order_payment__order__donations__project__projectpayout__id__in=payout_ids
        ).distinct()
Exemplo n.º 7
0
import decimal

from django.contrib import admin
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.utils.text import Truncator

from .models import ProjectPayoutLog, OrganizationPayoutLog

from .admin_filters import HasIBANPayoutFilter
from .admin_utils import link_to
from django import forms

PROJECT_PAYOUT_MODEL = get_project_payout_model()
ORGANIZATION_PAYOUT_MODEL = get_organization_payout_model()
MODEL_MAP = get_model_mapping()


class PayoutLogBase(admin.TabularInline):
    extra = 0
    max_num = 0
    can_delete = False
    fields = ['created', 'old_status', 'new_status']
    readonly_fields = fields


class PayoutLogInline(PayoutLogBase):
    model = ProjectPayoutLog
Exemplo n.º 8
0
    def form_valid(self, form):
        with db_transaction.atomic():
            order = get_order_model().objects.create(
                user=self.request.user, order_type="manual", total=form.cleaned_data["amount"]
            )

            form.instance.order = order
            form.instance.anonymous = True
            self.object = donation = form.save()

            order_payment = OrderPayment.objects.create(
                user=self.request.user, order=order, amount=donation.amount, payment_method="manual"
            )
            payment = ManualPayment.objects.create(
                amount=donation.amount,
                transaction=self.transaction,
                user=self.request.user,
                order_payment=order_payment,
            )

            # pull us through the statuses to consider it done
            payment.status = StatusDefinition.AUTHORIZED
            payment.save()
            payment.status = StatusDefinition.SETTLED
            payment.save()

            # update/create the required payout
            ProjectPayout = get_project_payout_model()
            project = donation.project
            project.update_amounts()
            payouts = ProjectPayout.objects.filter(project=project)

            # check the payouts and only update 'new' payouts, else create a new payout
            if payouts.exists():
                updateable = payouts.filter(status=StatusDefinition.NEW).first()  # only new payouts can be updated
                if updateable is None:
                    rules = payouts.values_list("payout_rule", flat=True).distinct()
                    if len(rules) == 1:
                        rule = rules[0]
                        _message = messages.success
                        msg = _("Created a new project payout with payment rule {rule}")
                    else:
                        _message = messages.warning
                        msg = _("There were {n} payout rules, the choosen rule was: '{rule}'")

                    # create a new payout, since the other payouts are on their way for processing and can't be altered
                    payout = ProjectPayout(
                        planned=ProjectPayout.get_next_planned_date(), project=project, payout_rule=rule, protected=True
                    )

                    # we need to manually calculate the amounts, else all project donations will be taken into account
                    # FIXME: this needs to be refactored on the BB_PAYOUT level!
                    calculator = payout.get_calculator()
                    payout.calculate_payable_and_fee(calculator, donation.amount)
                    payout.save()
                    rule = dict(ProjectPayout.PayoutRules.choices)[rule]
                    _message(self.request, msg.format(n=len(rules), rule=rule))
                else:
                    # there is a payout that is still 'new', update that one
                    if updateable.protected is False:
                        updateable.calculate_amounts()
                    else:
                        # this is already an 'irregular' payout, so add the diff
                        # manually. There should be a journal entry for the modification
                        updateable.amount_raised += donation.amount
                        calculator = updateable.get_calculator()
                        updateable.calculate_payable_and_fee(calculator, updateable.get_amount_raised())
                        updateable.save()

                    messages.success(
                        self.request, _("Created a manual donation and updated project payout %r") % updateable
                    )

                # NOTE theoretically there is a situation where the only updateable
                # payout is a payout for an open project and the payout is protected
                # It would then theoretically be possible to make a new donation via
                # the web interface and the project payout will not be re-calculateable

            self.transaction.status = BankTransaction.IntegrityStatus.Valid
            self.transaction.save()

            # TODO FIXME: organization payouts?! recalculate?

        return redirect(self.get_success_url())