File: wise.py

package info (click to toggle)
ledger-autosync 1.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 508 kB
  • sloc: python: 2,711; makefile: 5
file content (141 lines) | stat: -rw-r--r-- 4,747 bytes parent folder | download
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"]))