Skip to content

Commit

Permalink
Add fallback account for payment spend
Browse files Browse the repository at this point in the history
These are Credit Card payments, so it does not make sense to require
them to be linked to one account.

Fixes #71
  • Loading branch information
Nef10 committed Sep 20, 2021
1 parent 8325ee3 commit 5e1db9b
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 28 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ For accounts used in transactions to and from your Wealthsimple accounts you nee
* Use `wealthsimple-fee` on an expense account to track the wealthsimple fees
* Use `wealthsimple-non-resident-withholding-tax` on an expense account for non resident withholding tax
* In case some transaction does not balance within your ledger, an expense account with `wealthsimple-rounding` will get the difference
* If you want to track contribution room, use `wealthsimple-contribution-room` on an asset and expense account (optional)
* If you want to track contribution room, use `wealthsimple-contribution-room` on an asset and expense account (optional, if not set it will not create postings for the contribution room)
* Other values for transaction types you might incur are:
* `wealthsimple-reimbursement`
* `wealthsimple-interest`
Expand All @@ -45,7 +45,7 @@ For accounts used in transactions to and from your Wealthsimple accounts you nee
* `wealthsimple-referral-bonus`
* `wealthsimple-giveaway-bonus`
* `wealthsimple-refund`
* `wealthsimple-payment-spend`
* `wealthsimple-payment-spend` (optional, will use fallback account if not provided)

<details>
<summary>Full Example</summary>
Expand Down Expand Up @@ -86,7 +86,11 @@ For accounts used in transactions to and from your Wealthsimple accounts you nee

## How

Please check out the complete documentation [here](https://nef10.github.io/SwiftBeanCountWealthsimpleMapper/). You can also have a look at the [SwiftBeanCountDownloaderApp](https://github.com/Nef10/SwiftBeanCountDownloaderApp) which uses this library.
1) First create an instance of the mapper via `WealthsimpleLedgerMapper(ledger:)`, passing the ledger which contains the meta data discussed above.
2) Assign the downloaded wealthsimple accounts to the `accounts` property on the mapper.
3) Call `mapPositionsToPriceAndBalance` or `mapTransactionsToPriceAndTransactions` to map your downloaded positions / transactions to SwiftBeanCountModel Prices and Balances / Prices and Transactions.

Please also check out the complete documentation [here](https://nef10.github.io/SwiftBeanCountWealthsimpleMapper/). Additionally, you can have a look at the [SwiftBeanCountDownloaderApp](https://github.com/Nef10/SwiftBeanCountDownloaderApp) which uses this library.

## Usage

Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftBeanCountWealthsimpleMapper/LedgerLookup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ struct LedgerLookup {
key = MetaDataKeys.rounding
}
guard let name = ledger.accounts.first(where: { accountTypes.contains($0.name.accountType) && $0.metaData[key]?.contains(account.number) ?? false })?.name else {
if case .transactionType(.paymentSpend) = type {
return WealthsimpleLedgerMapper.fallbackExpenseAccountName
}
throw WealthsimpleConversionError.missingAccount(key, account.number, accountTypes.map { $0.rawValue }.joined(separator: ", or "))
}
return name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public struct WealthsimpleLedgerMapper {
private typealias WTransaction = Wealthsimple.Transaction
private typealias STransaction = SwiftBeanCountModel.Transaction

/// Fallback account for payments if not account with the correct meta data could be found
///
/// Only used for transaction type payment spend
public static let fallbackExpenseAccountName = try! AccountName("Expenses:TODO") // swiftlint:disable:this force_try

/// Payee used for fee transactions
private static let payee = "Wealthsimple"

Expand Down
48 changes: 23 additions & 25 deletions Tests/SwiftBeanCountWealthsimpleMapperTests/LedgerLookupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ struct TestAccount: AccountProvider {

final class LedgerLookupTests: XCTestCase {

private let accountName = try! AccountName("Assets:Test")

func testLedgerAccountCommoditySymbol() {
let name1 = try! AccountName("Assets:Test")
let name2 = try! AccountName("Assets:Test1")
let symbol = "CAD"
let ledger = Ledger()
var ledgerLookup = LedgerLookup(ledger)

// account does not exist
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: name1))
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: accountName))

// account does not have a commodity
try! ledger.add(Account(name: name1))
try! ledger.add(Account(name: accountName))
ledgerLookup = LedgerLookup(ledger)
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: name1))
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: accountName))

// account has a commodity
try! ledger.add(Account(name: name2, commoditySymbol: symbol))
ledgerLookup = LedgerLookup(ledger)
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: name1))
XCTAssertNil(ledgerLookup.ledgerAccountCommoditySymbol(of: accountName))
XCTAssertEqual(ledgerLookup.ledgerAccountCommoditySymbol(of: name2), symbol)

}

func testLedgerAccountNameOf() {
let name1 = try! AccountName("Assets:Test")
let account = TestAccount(number: "abc")
let ledger = Ledger()
var ledgerLookup = LedgerLookup(ledger)
Expand All @@ -48,9 +48,9 @@ final class LedgerLookupTests: XCTestCase {

// base account
try! ledger.add(Commodity(symbol: "XGRO"))
try! ledger.add(Account(name: name1, metaData: ["importer-type": "wealthsimple", "number": "abc"]))
try! ledger.add(Account(name: accountName, metaData: ["importer-type": "wealthsimple", "number": "abc"]))
ledgerLookup = LedgerLookup(ledger)
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(of: account), name1)
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(of: account), accountName)

// commodity account
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(of: account, symbol: "XGRO"), try! AccountName("Assets:XGRO"))
Expand All @@ -66,28 +66,27 @@ final class LedgerLookupTests: XCTestCase {
func testLedgerAccountNameFor() {
let ledger = Ledger()
var ledgerLookup = LedgerLookup(ledger)
var name = try! AccountName("Assets:Test")
var number = "abc123"

// fallback for payment spend
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(for: .transactionType(.paymentSpend), in: TestAccount(number: number), ofType: [.expense] ),
WealthsimpleLedgerMapper.fallbackExpenseAccountName)

// not found
assert(
try ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.income]),
throws: WealthsimpleConversionError.missingAccount(MetaDataKeys.rounding, number, "Income")
)
assert(try ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.income]),
throws: WealthsimpleConversionError.missingAccount(MetaDataKeys.rounding, number, "Income"))

// rounding
try! ledger.add(Account(name: name, metaData: [MetaDataKeys.rounding: number]))
try! ledger.add(Account(name: accountName, metaData: [MetaDataKeys.rounding: number]))
ledgerLookup = LedgerLookup(ledger)
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.asset] ), name)
XCTAssertEqual(try! ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.asset] ), accountName)

// wrong type
assert(
try ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.income, .expense, .equity] ),
throws: WealthsimpleConversionError.missingAccount(MetaDataKeys.rounding, number, "Income, or Expenses, or Equity")
)
assert(try ledgerLookup.ledgerAccountName(for: .rounding, in: TestAccount(number: number), ofType: [.income, .expense, .equity] ),
throws: WealthsimpleConversionError.missingAccount(MetaDataKeys.rounding, number, "Income, or Expenses, or Equity"))

// multiple numbers
name = try! AccountName("Assets:Test:Two")
var name = try! AccountName("Assets:Test:Two")
number = "def456"
let number2 = "ghi789"
try! ledger.add(Account(name: name, metaData: [MetaDataKeys.rounding: "\(number) \(number2)"]))
Expand Down Expand Up @@ -171,24 +170,23 @@ final class LedgerLookupTests: XCTestCase {
func testDoesBalanceExistInLedger() {
let ledger = Ledger()
let date = Date()
let name = try! AccountName("Assets:TEST")
var balance = Balance(date: date, accountName: name, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
var balance = Balance(date: date, accountName: accountName, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
ledger.add(balance)
let ledgerLookup = LedgerLookup(ledger)

// same balance
XCTAssert(ledgerLookup.doesBalanceExistInLedger(balance))

// different balance object with same properties
balance = Balance(date: date, accountName: name, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
balance = Balance(date: date, accountName: accountName, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
XCTAssert(ledgerLookup.doesBalanceExistInLedger(balance))

// different date
balance = Balance(date: Date(timeIntervalSinceReferenceDate: 0), accountName: name, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
balance = Balance(date: Date(timeIntervalSinceReferenceDate: 0), accountName: accountName, amount: Amount(number: Decimal(1), commoditySymbol: "USD"))
XCTAssertFalse(ledgerLookup.doesBalanceExistInLedger(balance))

// different commodity
balance = Balance(date: date, accountName: name, amount: Amount(number: Decimal(1), commoditySymbol: "EUR"))
balance = Balance(date: date, accountName: accountName, amount: Amount(number: Decimal(1), commoditySymbol: "EUR"))
XCTAssertFalse(ledgerLookup.doesBalanceExistInLedger(balance))

// different account
Expand Down

0 comments on commit 5e1db9b

Please sign in to comment.