Skip to content

Commit

Permalink
Gnucash: importing multicurrency simple transfers is now possible
Browse files Browse the repository at this point in the history
  • Loading branch information
sejtenik committed Apr 28, 2009
1 parent ac2366a commit d8bc1bd
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 40 deletions.
2 changes: 1 addition & 1 deletion app/models/category.rb
Expand Up @@ -32,7 +32,7 @@ class Category < ActiveRecord::Base

attr_accessor :opening_balance, :opening_balance_currency, :new_subcategories

attr_accessor :parent_guid #for importing, not saved in db
attr_accessor :parent_guid, :import_currency #for importing, not saved in db

belongs_to :user

Expand Down
4 changes: 2 additions & 2 deletions app/models/exchange.rb
Expand Up @@ -83,11 +83,11 @@ def self.switch(a,b)

def exchange(amount, currency)
if left_currency == currency
return amount*right_to_left
return (amount*right_to_left).to_f.round(2)
end

if right_currency == currency
return amount*left_to_right
return (amount*left_to_right).to_f.round(2)
end

raise 'Wrong currency given'
Expand Down
2 changes: 1 addition & 1 deletion app/views/users/edit.html.erb
Expand Up @@ -82,7 +82,7 @@
</legend>

<%= label_tag 'password', 'Musisz podać swoje hasło aby usunąć konto' %>
<%= text_field_tag 'password' %>
<%= password_field_tag 'password' %>
<%= submit_tag 'Usuń konto i wszystkie wprowadzone dane' %>
</fieldset>
<% end %>
Expand Down
44 changes: 26 additions & 18 deletions lib/gnucash_parser.rb
Expand Up @@ -47,8 +47,8 @@ class << self
def parse(content, user)
doc = Nokogiri::XML(content)
result = {}
result[:categories] = import_categories(user, doc)
result[:transfers] = import_transfers(user, doc)
result[:categories], imported_categories_hash = import_categories(user, doc)
result[:transfers] = import_transfers(user, doc, imported_categories_hash)
result
rescue GnuCashParseError
raise
Expand All @@ -67,7 +67,6 @@ def import_categories(user, doc)
top_categories = {}
progress "\n==Parsing categories"
doc.find('//gnc:account').each do |node|
# logger.debug "Commodity: " + node.find('act:commodity/cmdty:id').inner_text
c = Category.new
c.import_guid = node.find('act:id').inner_text
c.name = node.find('act:name').inner_text
Expand All @@ -76,6 +75,7 @@ def import_categories(user, doc)
type = node.find('act:type').inner_text
c.user = user
c.imported = true
c.import_currency = node.find('act:commodity/cmdty:id').inner_text

if !root_category && type == 'ROOT'
root_category = c
Expand Down Expand Up @@ -104,10 +104,13 @@ def import_categories(user, doc)
item.parent_guid = nil
end
elsif top_categories[category_type].size == 1
top_gc_guid = top_categories[category_type][0].import_guid
category_being_merged = top_categories[category_type].first
top_gc_guid = category_being_merged.import_guid
top.import_guid = top_gc_guid
top.import_currency = category_being_merged.import_currency
top.name = category_being_merged.name
top.description = category_being_merged.description
categories[top_gc_guid] = top
#mozna ewentualnie podmienic nazwe i opis
end
end
end
Expand Down Expand Up @@ -147,18 +150,17 @@ def import_categories(user, doc)
result[:added] = saved
result[:merged] = merged
progress "\n" + result.inspect + "\n"
return result
return result, categories
end


def import_transfers(user, doc)
def import_transfers(user, doc, categories = {})
result = {}
transaction_count = doc.find('//gnc:count-data[@cd:type="transaction"]').inner_text.to_i
saved = 0
progress "\n==Parsing and saving transfers"
result[:errors] = []
doc.find('//gnc:transaction').each do |node|
multi_currency_transfer = false
t = Transfer.new
t.user = user
t.import_guid = node.find('trn:id').inner_text
Expand All @@ -172,7 +174,7 @@ def import_transfers(user, doc)
ti = TransferItem.new
ti.import_guid = split.find('split:id').inner_text
category_guid = split.find('split:account').inner_text
ti.category = user.categories.find_by_import_guid(category_guid)
ti.category = categories[category_guid]# || user.categories.find_by_import_guid(category_guid)
ti.description = split.find('split:memo').inner_text
value_str = split.find('split:value').inner_text
value = parse_value(value_str)
Expand All @@ -182,20 +184,26 @@ def import_transfers(user, doc)

if (value == quantity)
ti.value = value
ti.currency = currency
else
multi_currency_transfer = true
break
ti.value = quantity
foreign_currency = find_or_create_currency(ti.category.import_currency, user)
ti.currency = foreign_currency

t.conversions.build(:exchange => Exchange.new(
:left_currency => currency,
:right_currency => foreign_currency,
:left_to_right => (quantity/value).to_f.round(4),
:right_to_left => (value/quantity).to_f.round(4),
:user => user
))
#FIXME: there will be validation problem if there exist more than one conversion between same currencies
end

ti.currency = currency

t.transfer_items << ti
end

if multi_currency_transfer
result[:errors] << ["#{t.day} #{t.description}: transakcje wielowalutowe nie są obsługiwane podczas importu"]
next
end

if t.save
progress
saved += 1
Expand Down Expand Up @@ -263,7 +271,7 @@ def find_or_create_currency(long_symbol, user)
def parse_value(value_str)
value = nil
if value_str =~ /(-?\d*)\/(\d*)/
value = $1.to_f / $2.to_f
value = ($1.to_f / $2.to_f).to_f.round(2)
else
progress "Problems parsing #{value_str} value"
end
Expand Down
70 changes: 61 additions & 9 deletions test/files/gnucash_with_currencies.xml
Expand Up @@ -15,25 +15,25 @@
xmlns:bgt="http://www.gnucash.org/XML/bgt"
xmlns:recurrence="http://www.gnucash.org/XML/recurrence"
xmlns:lot="http://www.gnucash.org/XML/lot"
xmlns:cust="http://www.gnucash.org/XML/cust"
xmlns:job="http://www.gnucash.org/XML/job"
xmlns:invoice="http://www.gnucash.org/XML/invoice"
xmlns:addr="http://www.gnucash.org/XML/addr"
xmlns:owner="http://www.gnucash.org/XML/owner"
xmlns:taxtable="http://www.gnucash.org/XML/taxtable"
xmlns:tte="http://www.gnucash.org/XML/tte"
xmlns:employee="http://www.gnucash.org/XML/employee"
xmlns:order="http://www.gnucash.org/XML/order"
xmlns:cust="http://www.gnucash.org/XML/cust"
xmlns:billterm="http://www.gnucash.org/XML/billterm"
xmlns:bt-days="http://www.gnucash.org/XML/bt-days"
xmlns:bt-prox="http://www.gnucash.org/XML/bt-prox"
xmlns:invoice="http://www.gnucash.org/XML/invoice"
xmlns:taxtable="http://www.gnucash.org/XML/taxtable"
xmlns:tte="http://www.gnucash.org/XML/tte"
xmlns:order="http://www.gnucash.org/XML/order"
xmlns:employee="http://www.gnucash.org/XML/employee"
xmlns:entry="http://www.gnucash.org/XML/entry"
xmlns:owner="http://www.gnucash.org/XML/owner"
xmlns:vendor="http://www.gnucash.org/XML/vendor">
<gnc:count-data cd:type="book">1</gnc:count-data>
<gnc:book version="2.0.0">
<book:id type="guid">70a267b98c1cd67d4489c3f4c51de856</book:id>
<gnc:count-data cd:type="commodity">1</gnc:count-data>
<gnc:count-data cd:type="account">7</gnc:count-data>
<gnc:count-data cd:type="account">8</gnc:count-data>
<gnc:count-data cd:type="transaction">3</gnc:count-data>
<gnc:commodity version="2.0.0">
<cmdty:space>ISO4217</cmdty:space>
Expand All @@ -42,6 +42,13 @@
<cmdty:quote_source>currency</cmdty:quote_source>
<cmdty:quote_tz/>
</gnc:commodity>
<gnc:commodity version="2.0.0">
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>EUR</cmdty:id>
<cmdty:get_quotes/>
<cmdty:quote_source>currency</cmdty:quote_source>
<cmdty:quote_tz/>
</gnc:commodity>
<gnc:commodity version="2.0.0">
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>PLN</cmdty:id>
Expand All @@ -56,6 +63,22 @@
<cmdty:fraction>10000</cmdty:fraction>
</gnc:commodity>
<gnc:pricedb version="1">
<price>
<price:id type="guid">d42007800a03db53fc60e834b9812627</price:id>
<price:commodity>
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>BGN</cmdty:id>
</price:commodity>
<price:currency>
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>PLN</cmdty:id>
</price:currency>
<price:time>
<ts:date>2009-04-28 00:00:00 +0200</ts:date>
</price:time>
<price:source>user:xfer-dialog</price:source>
<price:value>10/1</price:value>
</price>
<price>
<price:id type="guid">50353b58bbf586ecf533b15b80f2962f</price:id>
<price:commodity>
Expand All @@ -72,6 +95,22 @@
<price:source>user:xfer-dialog</price:source>
<price:value>10/1</price:value>
</price>
<price>
<price:id type="guid">290a1f84adedfc5ad1f346a156deaf21</price:id>
<price:commodity>
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>EUR</cmdty:id>
</price:commodity>
<price:currency>
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>BGN</cmdty:id>
</price:currency>
<price:time>
<ts:date>2009-04-28 00:00:00 +0200</ts:date>
</price:time>
<price:source>user:xfer-dialog</price:source>
<price:value>1/1</price:value>
</price>
<price>
<price:id type="guid">44984fe4f50c11a1b67dd05386e20fb7</price:id>
<price:commodity>
Expand Down Expand Up @@ -119,6 +158,17 @@
<act:commodity-scu>100</act:commodity-scu>
<act:parent type="guid">3b174d481178f04b84933df9983a0f7a</act:parent>
</gnc:account>
<gnc:account version="2.0.0">
<act:name>other currency account</act:name>
<act:id type="guid">b993284b76a35a9e6ce41df583fe2462</act:id>
<act:type>ASSET</act:type>
<act:commodity>
<cmdty:space>ISO4217</cmdty:space>
<cmdty:id>EUR</cmdty:id>
</act:commodity>
<act:commodity-scu>100</act:commodity-scu>
<act:parent type="guid">3b174d481178f04b84933df9983a0f7a</act:parent>
</gnc:account>
<gnc:account version="2.0.0">
<act:name>Income</act:name>
<act:id type="guid">9e4f067fdd36e1114b228aa619fbc3e8</act:id>
Expand Down Expand Up @@ -183,13 +233,15 @@
<trn:splits>
<trn:split>
<split:id type="guid">bf79e884b39c227d4de52543c47bbe03</split:id>
<split:memo>PLN TI</split:memo>
<split:reconciled-state>n</split:reconciled-state>
<split:value>1000/100</split:value>
<split:quantity>10000/100</split:quantity>
<split:quantity>10123/100</split:quantity>
<split:account type="guid">85485a00d6f50e55af7f618ad46024b9</split:account>
</trn:split>
<trn:split>
<split:id type="guid">23573b02e2b6d23ca16bfb1181a0c56c</split:id>
<split:memo>BGN TI</split:memo>
<split:reconciled-state>c</split:reconciled-state>
<split:value>-1000/100</split:value>
<split:quantity>-1000/100</split:quantity>
Expand Down
2 changes: 1 addition & 1 deletion test/functional/import_controller_test.rb
Expand Up @@ -36,7 +36,7 @@ def setup
assert_select 'div#parsing-good'

assert_select "ul#category-error li", :count => 1
assert_select "ul#transfer-error li", :count => 5
assert_select "ul#transfer-error li", :count => 4
end


Expand Down
36 changes: 28 additions & 8 deletions test/unit/gnucash_parser_test.rb
Expand Up @@ -126,25 +126,45 @@ def setup
test "Parse file with multi currencies" do
result = nil
load_file 'gnucash_with_currencies' do |content|
assert_difference("@jarek.categories.count", +1) do
assert_difference("@jarek.transfers.count", +2) do
assert_difference("@jarek.transfer_items.count", +4) do
assert_difference("@jarek.categories.count", +2) do
assert_difference("@jarek.transfers.count", +3) do
assert_difference("@jarek.transfer_items.count", +6) do
result = GnucashParser.parse(content, @jarek)
end
end
end
end

assert_equal 6, result[:categories][:in_file]
assert_equal 1, result[:categories][:added]
assert_equal 7, result[:categories][:in_file]
assert_equal 2, result[:categories][:added]
assert_equal 5, result[:categories][:merged]
assert_equal 0, result[:categories][:errors].size

assert_equal 3, result[:transfers][:in_file]
assert_equal 2, result[:transfers][:added]
assert_equal 1, result[:transfers][:errors].size
assert_equal 3, result[:transfers][:added]
assert_equal 0, result[:transfers][:errors].size

assert_match(/transakcje wielowalutowe nie są obsługiwane/, result[:transfers][:errors].first.first)
transfer2 = @jarek.transfers.find_by_description 'Test currency transfer'
assert_not_nil transfer2
assert_equal 2, transfer2.transfer_items.size

exchange = transfer2.conversions.first.exchange
assert_not_nil exchange
assert_equal @jarek, exchange.user
assert_equal @zloty, exchange.left_currency
assert_equal 'BGN', exchange.right_currency.long_symbol
assert_equal 0.0988, exchange.left_to_right
assert_equal 10.1230, exchange.right_to_left

tranfer_item_21 = transfer2.transfer_items.find_by_description 'PLN TI'
assert_not_nil tranfer_item_21
assert_equal 101.23, tranfer_item_21.value
assert_equal @zloty, tranfer_item_21.currency

tranfer_item_22 = transfer2.transfer_items.find_by_description 'BGN TI'
assert_not_nil tranfer_item_22
assert_equal(-10, tranfer_item_22.value)
assert_equal 'BGN', tranfer_item_22.currency.long_symbol



Expand Down

0 comments on commit d8bc1bd

Please sign in to comment.