|
|
|
@@ -0,0 +1,124 @@ |
|
|
|
import ynab |
|
|
|
import logging |
|
|
|
|
|
|
|
def select_budget(api_client): |
|
|
|
budgets_api = ynab.BudgetsApi(api_client) |
|
|
|
budgets = budgets_api.get_budgets().data.budgets |
|
|
|
|
|
|
|
if len(budgets) == 1: |
|
|
|
budget = budgets[0] |
|
|
|
else: |
|
|
|
budget_options = ", ".join( |
|
|
|
[ |
|
|
|
f"{i}: {budget.name}" |
|
|
|
for i, budget in enumerate(budgets) |
|
|
|
if 'Archived' not in budget.name |
|
|
|
] |
|
|
|
) |
|
|
|
budget_index = input(f"Which budget? ({budget_options}) ") |
|
|
|
budget = budgets[int(budget_index)] |
|
|
|
|
|
|
|
return budget |
|
|
|
|
|
|
|
|
|
|
|
def select_account(api_client, budget): |
|
|
|
accounts_api = ynab.AccountsApi(api_client) |
|
|
|
accounts = accounts_api.get_accounts(budget.id).data.accounts |
|
|
|
|
|
|
|
if len(accounts) == 1: |
|
|
|
account = accounts[0] |
|
|
|
else: |
|
|
|
account_options = ", ".join( |
|
|
|
[ |
|
|
|
f"{i}: {account.name}" |
|
|
|
for i, account in enumerate(accounts) |
|
|
|
if not account.closed |
|
|
|
] |
|
|
|
) |
|
|
|
account_index = input(f"Which account? ({account_options}) ") |
|
|
|
account = accounts[int(account_index)] |
|
|
|
|
|
|
|
return account |
|
|
|
|
|
|
|
|
|
|
|
def add_transactions(api_client, budget, account, data): |
|
|
|
transaction_api = ynab.TransactionsApi(api_client) |
|
|
|
|
|
|
|
transactions = [] |
|
|
|
skipped = 0 |
|
|
|
amount_factor = 1000 |
|
|
|
|
|
|
|
start_date = data['Date'].min() |
|
|
|
|
|
|
|
logging.debug(f"Looking for existing transactions since {start_date}") |
|
|
|
existing_transactions = transaction_api.get_transactions_by_account(budget.id, account.id, since_date=start_date).data.transactions |
|
|
|
|
|
|
|
for i, row in data.iterrows(): |
|
|
|
try: |
|
|
|
date = row['Date'].date() |
|
|
|
import_id = f"IMPORT:{date}:{row['Inflow']}:{row['Outflow']}:{i}" |
|
|
|
amount = int((row['Inflow'] - row['Outflow']) * amount_factor) |
|
|
|
|
|
|
|
for existing in existing_transactions: |
|
|
|
skip = False |
|
|
|
if existing.import_id == import_id: |
|
|
|
skip = True |
|
|
|
elif existing.var_date == date and existing.amount == amount and (existing.cleared == 'cleared') == row['Cleared']: |
|
|
|
if (row['Payee'].lower() in existing.payee_name.lower()) or (row['Memo'] in existing.memo.lower()): |
|
|
|
skip = True |
|
|
|
else: |
|
|
|
skip = input(f"On {date}, is {row['Payee']} {row['Memo']} the same as {existing.payee_name} {existing.memo} (y/N)").lower() == 'y' |
|
|
|
|
|
|
|
if skip: |
|
|
|
skipped += 1 |
|
|
|
break |
|
|
|
else: |
|
|
|
transaction = ynab.models.new_transaction.NewTransaction( |
|
|
|
account_id = account.id, |
|
|
|
var_date = date, |
|
|
|
amount = amount, |
|
|
|
payee_name = row['Payee'], |
|
|
|
memo = row['Memo'], |
|
|
|
cleared = 'cleared' if row['Cleared'] else 'uncleared', |
|
|
|
import_id = import_id if row['Cleared'] else None, |
|
|
|
) |
|
|
|
|
|
|
|
transactions.append(transaction) |
|
|
|
except: |
|
|
|
logging.error(f"Could not format transaction {row} for YNAB") |
|
|
|
raise |
|
|
|
|
|
|
|
if skipped > 0: |
|
|
|
print(f"Skipped {skipped} existing transactions") |
|
|
|
|
|
|
|
if len(transactions) == 0: |
|
|
|
print("No transactions to import") |
|
|
|
return |
|
|
|
|
|
|
|
print(f"Adding {len(transactions)} transactions to account {account.name} in budget {budget.name}:") |
|
|
|
print(f"{'Date':<12}{'Payee':<50}{'Memo':<50}{'Amount':10}{'Status':<11}{'Import ID'}") |
|
|
|
for transaction in transactions: |
|
|
|
print(f"{transaction.var_date} {transaction.payee_name:<50}{transaction.memo:<50}{transaction.amount/amount_factor:<10}{transaction.cleared:<11}{transaction.import_id}") |
|
|
|
|
|
|
|
confirmed = input(f"Import transactions to {budget.name} - {account.name}? (y/N) ") |
|
|
|
if confirmed.lower() != 'y': |
|
|
|
print("Stopping processing") |
|
|
|
exit(1) |
|
|
|
|
|
|
|
transaction_data = ynab.PostTransactionsWrapper() |
|
|
|
transaction_data.transactions = transactions |
|
|
|
response = transaction_api.create_transaction(budget.id, transaction_data) |
|
|
|
|
|
|
|
logging.debug(f"Created transactions") |
|
|
|
|
|
|
|
|
|
|
|
def write_to_api(file_path, bank_name, ynab_data, api_token): |
|
|
|
configuration = ynab.Configuration( |
|
|
|
access_token = api_token |
|
|
|
) |
|
|
|
|
|
|
|
with ynab.ApiClient(configuration) as api_client: |
|
|
|
print(f"Pushing transactions from {bank_name} ({file_path}) to YNAB") |
|
|
|
budget = select_budget(api_client) |
|
|
|
account = select_account(api_client, budget) |
|
|
|
add_transactions(api_client, budget, account, ynab_data) |