1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
# ledger-autosync plugin for CSV files from Wise, formerly TransferWise.
# You must download and sync a CSV statement for all currencies that
# cover the same time period. Since Wise lists the transactions in
# reverse chronological order, you might want to run with --reverse flag.
# The code assumes some text is in English. Adjust mk_currency() and
# anything else to suit. No warranty, YMMV, etc.
import datetime
from decimal import Decimal
import re
from ledgerautosync.converter import (
Amount,
Converter,
CsvConverter,
Posting,
Transaction,
)
class WiseConverter(CsvConverter):
FIELDSET = set(
[
"TransferWise ID",
"Date",
"Amount",
"Currency",
"Description",
"Payment Reference",
"Running Balance",
"Exchange From",
"Exchange To",
"Exchange Rate",
"Payer Name",
"Payee Name",
"Payee Account Number",
"Merchant",
"Card Last Four Digits",
"Card Holder Full Name",
"Attachment",
"Note",
"Total fees",
]
)
def __init__(self, *args, **kwargs):
super(WiseConverter, self).__init__(*args, **kwargs)
def mk_currency(self, currency):
if currency == "USD":
currency = "$"
elif currency == "GBP":
currency = "£"
elif currency == "EUR":
currency = "€"
return currency
def mk_amount(self, amt, currency, reverse=False):
currency = self.mk_currency(currency)
return Amount(Decimal(amt), currency, reverse=reverse)
def convert(self, row):
tid = row["TransferWise ID"]
checknum = int(tid.split("-")[1])
amt = Decimal(row["Amount"])
acct_from = self.name
curr_from = self.mk_currency(row["Currency"])
acct_to = "Expenses:Misc"
curr_to = curr_from
amt_from = Amount(amt, curr_from)
amt_to = Amount(amt, curr_to, reverse=True)
fee_not_included = (
tid.startswith("CARD-") and row["Currency"] == "USD" and row["Exchange To"]
)
if row["Exchange To"]:
rate = Decimal(row["Exchange Rate"])
curr = self.mk_currency(row["Currency"])
curr_from = self.mk_currency(row["Exchange From"])
curr_to = self.mk_currency(row["Exchange To"])
if curr == curr_from:
amt_from = Amount(amt, curr_from)
# Card transactions from USD to other currencies do not consider the fees in the exchange rate
amt_to = Amount(
(
amt
+ (
Decimal(row["Total fees"])
if tid.startswith("CARD-")
and not (row["Currency"] == "USD" and row["Exchange To"])
else Decimal(0)
)
)
* rate,
curr_to,
reverse=True,
)
acct_from = self.name
else:
# Do not import this exchange from this statement; instead use the statement for the matching "from" currency
if tid.startswith("BALANCE-"):
return ""
amt_from = Amount(amt / rate, curr_from, reverse=True)
amt_to = Amount(amt, curr_to)
acct_from = self.name if tid.startswith("BALANCE-") else "Expenses:Misc"
if tid.startswith("BALANCE-"):
acct_to = self.name
if row["Description"].startswith("Wise Charges for:"):
acct_to = "Expenses:Bank Charges"
if tid.startswith("TRANSFER-"):
payee = row["Payee Name"] or row["Payer Name"]
elif tid.startswith("BALANCE-"):
payee = row["Description"]
else:
payee = row["Merchant"]
meta = {"csvid": self.get_csv_id(row)}
posting_from = Posting(acct_from, amt_from, metadata=meta)
posting_to = Posting(acct_to, amt_to)
return Transaction(
date=datetime.datetime.strptime(row["Date"], "%d-%m-%Y"),
cleared=True,
date_format="%Y-%m-%d",
checknum=checknum,
payee=payee,
postings=[posting_to, posting_from],
)
def get_csv_id(self, row):
fmt = (
"wise.fee.{}"
if row["Description"].startswith("Wise Charges for:")
else "wise.{}"
)
return fmt.format(Converter.clean_id(row["TransferWise ID"]))
|