No release in over 3 years
Low commit activity in last 3 years
Generate ledger transactions from Tiller
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 13.0
~> 3.0
>= 0

Runtime

 Project Readme

LedgerTillerExport

LedgerTillerExport is a tool to help export transactions from Tiller into Ledger. It reads the Google spreadsheet that Tiller maintains and follows rules that you define to create Ledger transactions. It will read your Ledger file to dedupe transactions using Tiller's guids.

Installation

Add this line to your application's Gemfile:

gem 'ledger_tiller_export'

And then execute:

$ bundle

Or install it yourself as:

$ gem install ledger_tiller_export

Usage

Here's a simple example script:

require 'ledger_tiller_export'

RULES = [
  LedgerTillerExport::RegexpRule.new(match: 'McDonalds', account: 'Expenses:Food'),
  LedgerTillerExport::RegexpRule.new(match: 'DTE', account: 'Expenses:Utils:Energy'),
]

if __FILE__ == $PROGRAM_NAME
  LedgerTillerExport::Exporter.new(
    rules: RULES,
    spreadsheet: 'YourGoogleSheetId',
  ).run
end

If you already have existing Ledger transactions you may want to pick a start date and only export transactions that happen after that date. You can subclass LedgerTillerExport::Exporter and override the skip_row? method:

class MyTillerExporter < LedgerTillerExport::Exporter
  def skip_row?(row)
    return true if row.txn_date < Date.new(2020, 5, 1)

    super
  end
end

LedgerTillerExport comes with one rule type, RegexpRule, which simply matches on the payee of each transaction and returns the given account. You can define your own rule types if you want to implement more interesting behavior. For example, I track checks that I've written using transactions like this:

2020/05/05 * Lawn Guy
    Expenses:Lawn                      $35.00
    Liabilities:Checks:SomeBank:1025

When the check gets cashed I write another transaction like this:

2020/05/10 * Check paid
    Liabilities:Checks:SomeBank:1025   $35.00
    Assets:SomeBank::Checking

To automatically reconcile these I have a rule in my exporter script that looks like this:

require 'sorbet-runtime'

class CheckingAccountRule
  extend T::Sig

  include LedgerTillerExport::RuleInterface

  class CheckingAccount < T::Struct
    const :account, String
    const :check_account_prefix, String
  end

  class Check < T::Struct
    const :amount, Float
    const :check, String
  end
  
  CHECKING_ACCOUNTS = T.let([
    CheckingAccount.new(account: 'Assets:SomeBank:Checking', check_account_prefix: 'Liabilities:Checks:SomeBank'),
    CheckingAccount.new(account: 'Assets:OtherBank:Checking', check_account_prefix: 'Liabilities:Checks:OtherBank'),
  ], T::Array[CheckingAccount])

  sig {void}
  def initialize
    @outstanding_checks = fetch_outstanding_checks
  end

  sig {override.params(row: LedgerTillerExport::Row).returns(T.nilable(String))}
  def account_for_row(row)
    if row.description =~ /Check|Bill pay/i
      @outstanding_checks.each do |check|
        if row.amount.to_f.round(2) == check.amount.round(2)
          CHECKING_ACCOUNTS.each do |acct|
            if acct.account == row.account && check.check.start_with?(acct.check_account_prefix)
              return check.check
            end
          end
        end
      end
    end
  end

  # fetch all of the outstanding checks using a Ledger balance query with a custom format
  sig {returns(T::Array[Check])}
  def fetch_outstanding_checks
    cmd = [
      'ledger',
      %q{--balance-format=%(quantity(scrub(display_total))),%(account)\n},
      'bal',
      'checks',
    ]

    raw_checks, _, _ = Open3.capture3(*cmd)

    raw_checks.strip.split(/\n/).map do |raw|
      row = raw.split(/,/, 2)
      Check.new(amount: T.must(row.first).to_f, check: T.must(row.last))
    end
  end
end

The only required method is account_for_row, which either returns a String account name or nil if the rule didn't match.

This example rule will read all of the outstanding checks from Ledger using a custom balance query. It then will match against a specific payee (/Check|Bill pay/) and scan across all of the outstanding checks. If it finds one, it outputs the check account.

(Note that this uses Sorbet but you don't have to do that if you don't want to.)

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ledger_tiller_export. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the LedgerTillerExport project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.