Example #1
0
    def handle_method(self, method):
        pm = method.get_implementation()

        api = pm.get_api()

        for t in api.get_transactions():
            # We will re-fetch most transactions, so only create them if they are not
            # already there.
            trans, created = TransferwiseTransaction.objects.get_or_create(
                paymentmethod=method,
                twreference=t['referenceNumber'],
                defaults={
                    'datetime': api.parse_datetime(t['date']),
                    'amount': api.get_structured_amount(t['amount']),
                    'feeamount': api.get_structured_amount(t['totalFees']),
                    'transtype': t['details']['type'],
                    'paymentref': t['details']['paymentReference'][:200],
                    'fulldescription': t['details']['description'],
                })
            if created:
                # Set optional fields
                trans.counterpart_name = t['details'].get('senderName', '')
                trans.counterpart_account = t['details'].get(
                    'senderAccount', '').replace(' ', '')

                # Weird stuff that sometimes shows up
                if trans.counterpart_account == 'Unknownbankaccount':
                    trans.counterpart_account = ''

                if trans.counterpart_account:
                    # If account is IBAN, then try to validate it!
                    trans.counterpart_valid_iban = api.validate_iban(
                        trans.counterpart_account)
                trans.save()

                # If this is a refund transaction, process it as such
                if trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        '{0} refund'.format(settings.ORG_SHORTNAME)):
                    # Yes, this is one of our refunds. Can we find the corresponding transaction?
                    m = re.match(r'^TRANSFER-(\d+)$', t['referenceNumber'])
                    if not m:
                        raise Exception(
                            "Could not find TRANSFER info in transfer reference {0}"
                            .format(t['referenceNumber']))
                    transferid = m.groups(1)[0]
                    try:
                        twrefund = TransferwiseRefund.objects.get(
                            transferid=transferid)
                    except TransferwiseRefund.DoesNotExist:
                        print(
                            "Could not find transferwise refund for id {0}, registering as manual bank transaction"
                            .format(transferid))
                        register_bank_transaction(method, trans.id,
                                                  trans.amount,
                                                  trans.paymentref,
                                                  trans.fulldescription, False)
                        continue

                    if twrefund.refundtransaction or twrefund.completedat:
                        raise Exception(
                            "Transferwise refund for id {0} has already been processed!"
                            .format(transferid))

                    # Flag this one as done!
                    twrefund.refundtransaction = trans
                    twrefund.completedat = datetime.now()
                    twrefund.save()

                    invoicemanager = InvoiceManager()
                    invoicemanager.complete_refund(
                        twrefund.refundid,
                        trans.amount + trans.feeamount,
                        trans.feeamount,
                        pm.config('bankaccount'),
                        pm.config('feeaccount'),
                        [],  # urls
                        method,
                    )
                elif trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        '{0} returned payment'.format(settings.ORG_SHORTNAME)):
                    # Returned payment. Nothing much to do, but we create an accounting record
                    # for it just to make things nice and clear. But first make sure we can
                    # actually find the original transaction.
                    try:
                        po = TransferwisePayout.objects.get(
                            reference=trans.paymentref)
                    except TransferwisePayout.DoesNotExist:
                        raise Exception(
                            "Could not find transferwise payout object for {0}"
                            .format(trans.paymentref))

                    po.completedat = datetime.now()
                    po.completedtrans = trans
                    po.save()

                    m = re.match(
                        r'^{0} returned payment (\d+)$'.format(
                            settings.ORG_SHORTNAME), trans.paymentref)
                    if not m:
                        raise Exception(
                            "Could not find returned transaction id in reference '{0}'"
                            .format(trans.paymentref))
                    twtrans = TransferwiseTransaction.objects.get(
                        pk=m.groups(1)[0])
                    if twtrans.amount != -trans.amount - trans.feeamount:
                        raise Exception(
                            "Original amount {0} does not match returned amount {1}"
                            .format(twtrans.amount,
                                    -trans.amount - trans.feeamount))

                    accountingtxt = "TransferWise returned payment {0}".format(
                        trans.twreference)
                    accrows = [
                        (pm.config('bankaccount'), accountingtxt, trans.amount,
                         None),
                        (pm.config('feeaccount'), accountingtxt,
                         trans.feeamount, None),
                        (pm.config('bankaccount'), accountingtxt,
                         -(trans.amount + trans.feeamount), None),
                    ]
                    create_accounting_entry(date.today(), accrows)
                elif trans.transtype == 'TRANSFER' and trans.paymentref.startswith(
                        'TW payout'):
                    # Payout. Create an appropriate accounting record and a pending matcher.
                    try:
                        po = TransferwisePayout.objects.get(
                            reference=trans.paymentref)
                    except TransferwisePayout.DoesNotExist:
                        raise Exception(
                            "Could not find transferwise payout object for {0}"
                            .format(trans.paymentref))

                    refno = int(trans.paymentref[len("TW payout "):])

                    if po.amount != -(trans.amount + trans.feeamount):
                        raise Exception(
                            "Transferwise payout {0} returned transaction with amount {1} instead of {2}"
                            .format(refno, -(trans.amount + trans.feeamount),
                                    po.amount))

                    po.completedat = datetime.now()
                    po.completedtrans = trans
                    po.save()

                    # Payout exists at TW, so proceed to generate records. If the receiving account
                    # is a managed one, create a bank matcher. Otherwise just close the record
                    # immediately.
                    accrows = [
                        (pm.config('bankaccount'), trans.paymentref,
                         trans.amount, None),
                        (pm.config('feeaccount'), trans.paymentref,
                         trans.feeamount, None),
                        (pm.config('accounting_payout'), trans.paymentref,
                         -(trans.amount + trans.feeamount), None),
                    ]
                    if is_managed_bank_account(pm.config('accounting_payout')):
                        entry = create_accounting_entry(
                            date.today(), accrows, True)
                        register_pending_bank_matcher(
                            pm.config('accounting_payout'),
                            '.*TW.*payout.*{0}.*'.format(refno),
                            -(trans.amount + trans.feeamount), entry)
                    else:
                        create_accounting_entry(date.today(), accrows)
                else:
                    # Else register a pending bank transaction. This may immediately match an invoice
                    # if it was an invoice payment, in which case the entire process will copmlete.
                    register_bank_transaction(method, trans.id, trans.amount,
                                              trans.paymentref,
                                              trans.fulldescription,
                                              trans.counterpart_valid_iban)
Example #2
0
def bankfiles(request):
    authenticate_backend_group(request, 'Invoice managers')

    if request.method == 'POST':
        # Uploading a file!
        method = get_object_or_404(InvoicePaymentMethod,
                                   active=True,
                                   config__has_key='file_upload_interval',
                                   id=get_int_or_error(request.POST, 'id'))

        impl = method.get_implementation()
        # Stage 1 upload has the file in request.FILES. Stage 2 has it in a hidden field in the form instead,
        # because we can't make it upload th file twice.
        if 'fc' in request.POST:
            # In stage 2 the file is included ase base64
            txt = impl.convert_uploaded_file_to_utf8(
                BytesIO(base64.b64decode(request.POST['fc'])))

            try:
                rows = impl.parse_uploaded_file_to_rows(txt)
                (anyerror, extrakeys,
                 hasvaluefor) = impl.process_loaded_rows(rows)

                numrows = len(rows)
                numtrans = 0
                numpending = 0
                numerrors = 0

                with transaction.atomic():
                    # Store thef file itself
                    bankfile = BankFileUpload(
                        method=method,
                        parsedrows=numrows,
                        newtrans=0,
                        newpending=0,
                        errors=0,
                        uploadby=request.user.username,
                        name=request.POST['name'],
                        textcontents=txt,
                    )
                    bankfile.save()  # To get an id we can use

                    for r in rows:
                        if r['row_already_exists']:
                            continue
                        if r.get('row_errors', []):
                            numerrors += 1
                            continue

                        # Insert the row
                        b = BankStatementRow(
                            method=method,
                            fromfile=bankfile,
                            uniqueid=r.get('uniqueid', None),
                            date=r['date'],
                            amount=r['amount'],
                            description=r['text'],
                            balance=r.get('balance', None),
                            other=r['other'],
                        )
                        b.save()
                        numtrans += 1

                        if not register_bank_transaction(
                                b.method, b.id, b.amount, b.description, ''):
                            # This means the transaction wasn't directly matched and has been
                            # registered as a pending transaction.
                            numpending += 1

                    bankfile.newtrans = numtrans
                    bankfile.newpending = numpending
                    bankfile.errors = numerrors
                    bankfile.save()
            except Exception as e:
                messages.error(request, "Error uploading file: {}".format(e))

            return HttpResponseRedirect(".")

        if 'f' not in request.FILES:
            messages.error(request, "No file included in upload")
        elif request.FILES['f'].size < 1:
            messages.error(request, "Uploaded file is empty")
        else:
            f = request.FILES['f']

            try:
                # Stage 1, mean we parse it and render a second form to confirm
                rows = impl.parse_uploaded_file_to_rows(
                    impl.convert_uploaded_file_to_utf8(f))
                (anyerror, extrakeys,
                 hasvaluefor) = impl.process_loaded_rows(rows)

                f.seek(0)

                return render(
                    request, 'invoices/bankfile_uploaded.html', {
                        'method': method,
                        'rows': rows,
                        'extrakeys': sorted(extrakeys),
                        'hasvaluefor': hasvaluefor,
                        'anyerror': anyerror,
                        'filename': f.name,
                        'fc': base64.b64encode(f.read()).decode('ascii'),
                        'topadmin': 'Invoices',
                        'helplink': 'payment',
                        'breadcrumbs': [
                            ('../../', 'Bank files'),
                        ],
                    })
            except Exception as e:
                messages.error(request, "Error uploading file: {}".format(e))

    methods = InvoicePaymentMethod.objects.filter(
        active=True, config__has_key='file_upload_interval').annotate(
            latest_file=Max('bankfileupload__created'))
    file_objects = BankFileUpload.objects.select_related(
        'method').all().order_by('-created')[:1000]
    (files, paginator, page_range) = simple_pagination(request, file_objects,
                                                       50)

    return render(
        request, 'invoices/bankfiles.html', {
            'files': files,
            'page_range': page_range,
            'methods': methods,
            'topadmin': 'Invoices',
            'helplink': 'payment',
        })