From d8bc1bd61296b556cee8e6ab79358d93da4d2d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Pleba=C5=84ski?= Date: Tue, 28 Apr 2009 23:36:52 +0200 Subject: [PATCH] Gnucash: importing multicurrency simple transfers is now possible --- app/models/category.rb | 2 +- app/models/exchange.rb | 4 +- app/views/users/edit.html.erb | 2 +- lib/gnucash_parser.rb | 44 ++++++++------ test/files/gnucash_with_currencies.xml | 70 ++++++++++++++++++++--- test/functional/import_controller_test.rb | 2 +- test/unit/gnucash_parser_test.rb | 36 +++++++++--- 7 files changed, 120 insertions(+), 40 deletions(-) diff --git a/app/models/category.rb b/app/models/category.rb index 80af92c..6ab8019 100755 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -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 diff --git a/app/models/exchange.rb b/app/models/exchange.rb index de0334c..6ae8607 100755 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -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' diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 97b8d17..7eb5d32 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -82,7 +82,7 @@ <%= 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' %> <% end %> diff --git a/lib/gnucash_parser.rb b/lib/gnucash_parser.rb index d16b949..2517e17 100644 --- a/lib/gnucash_parser.rb +++ b/lib/gnucash_parser.rb @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 diff --git a/test/files/gnucash_with_currencies.xml b/test/files/gnucash_with_currencies.xml index c35b6f8..87a7b52 100644 --- a/test/files/gnucash_with_currencies.xml +++ b/test/files/gnucash_with_currencies.xml @@ -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"> 1 70a267b98c1cd67d4489c3f4c51de856 1 -7 +8 3 ISO4217 @@ -42,6 +42,13 @@ currency + + ISO4217 + EUR + + currency + + ISO4217 PLN @@ -56,6 +63,22 @@ 10000 + + d42007800a03db53fc60e834b9812627 + + ISO4217 + BGN + + + ISO4217 + PLN + + + 2009-04-28 00:00:00 +0200 + + user:xfer-dialog + 10/1 + 50353b58bbf586ecf533b15b80f2962f @@ -72,6 +95,22 @@ user:xfer-dialog 10/1 + + 290a1f84adedfc5ad1f346a156deaf21 + + ISO4217 + EUR + + + ISO4217 + BGN + + + 2009-04-28 00:00:00 +0200 + + user:xfer-dialog + 1/1 + 44984fe4f50c11a1b67dd05386e20fb7 @@ -119,6 +158,17 @@ 100 3b174d481178f04b84933df9983a0f7a + + other currency account + b993284b76a35a9e6ce41df583fe2462 + ASSET + + ISO4217 + EUR + + 100 + 3b174d481178f04b84933df9983a0f7a + Income 9e4f067fdd36e1114b228aa619fbc3e8 @@ -183,13 +233,15 @@ bf79e884b39c227d4de52543c47bbe03 + PLN TI n 1000/100 - 10000/100 + 10123/100 85485a00d6f50e55af7f618ad46024b9 23573b02e2b6d23ca16bfb1181a0c56c + BGN TI c -1000/100 -1000/100 diff --git a/test/functional/import_controller_test.rb b/test/functional/import_controller_test.rb index 53ec48e..5d51c2b 100644 --- a/test/functional/import_controller_test.rb +++ b/test/functional/import_controller_test.rb @@ -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 diff --git a/test/unit/gnucash_parser_test.rb b/test/unit/gnucash_parser_test.rb index 5869d14..ce3c1d4 100644 --- a/test/unit/gnucash_parser_test.rb +++ b/test/unit/gnucash_parser_test.rb @@ -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