def get_affected_records(self): """ Retrieve the donations and payouts that will be affected by taking a cut. """ affected = {} donations = self.object.local_payment.order_payment.order.donations.select_related( 'project' ) 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, ) new_payout.save() affected[donation] = { 'new_status': new_status, 'updateable_payout': updateable_payout, 'in_progress_payout': in_progress_payout, 'new_payout': new_payout } return affected
def export_sepa(self, request, queryset): """ Dowload a sepa file with selected ProjectPayments """ objs = queryset.all() if not request.user.is_staff: raise PermissionDenied response = HttpResponse() date = timezone.datetime.strftime(timezone.now(), '%Y%m%d%H%I%S') response['Content-Disposition'] = 'attachment; ' \ 'filename=payments_sepa%s.xml' % date response.write(ProjectPayout.create_sepa_xml(objs)) return response
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 bluebottle.payouts.models import ProjectPayout from localflavor.generic.validators import IBANValidator project = instance if project.status.slug in ['done-complete', 'done-incomplete'] \ and project.amount_asked: next_date = ProjectPayout.get_next_planned_date() payouts = ProjectPayout.objects.filter(project=project) if payouts.count(): # Get the latest payout payout = payouts.order_by('-created').all()[0] if payout.status == StatusDefinition.NEW: # Update planned payout date for new Payouts payout.calculate_amounts() payout.planned = next_date payout.save() else: if project.campaign_started: # Create new Payout payout = ProjectPayout( 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.name payout.save()
def form_valid(self, form): with db_transaction.atomic(): order = Order.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, bank_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 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())
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 bluebottle.payouts.models import ProjectPayout from localflavor.generic.validators import IBANValidator project = instance if project.status.slug in ['done-complete', 'done-incomplete'] \ and project.amount_asked: next_date = ProjectPayout.get_next_planned_date() payouts = ProjectPayout.objects.filter(project=project) if payouts.count(): # Get the latest payout payout = payouts.order_by('-created').all()[0] if payout.status == StatusDefinition.NEW: # Update planned payout date for new Payouts payout.calculate_amounts() payout.planned = next_date payout.save() else: if project.campaign_started: # Create new Payout payout = ProjectPayout(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_details = project.account_details or '' payout.receiver_account_number = project.account_number or '' payout.receiver_account_name = project.account_holder_name or '' payout.receiver_account_city = project.account_holder_city or '' try: payout.receiver_account_country = project.account_bank_country.name except AttributeError: payout.receiver_account_country = '' payout.save()