feidee_utils 0.0.4.3 → 0.0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd2d6afd486620f91184ac15f846ae7fef857b7c
4
- data.tar.gz: d0b20dca231364ca6ee87392c9c57b69220c81d0
3
+ metadata.gz: 9cc0908a9717145ac9f863abad4f5d4ff910a898
4
+ data.tar.gz: 64d25e89e933a547ba71e6e412b64178dc7a9310
5
5
  SHA512:
6
- metadata.gz: addacf49613d3cfb3be9a830046046abc84ace8091a514589bdbd46c01f09437dbeee95c00f2ba147e1986096e64b077171f3f2105a782df9821e63de998860b
7
- data.tar.gz: 0fb69c197627fc5ddacf43bf956fe2649aaacd18159812fd8f0cb903be5de9755faf6489e141e4bdb06f5cc72d8ec84e5a8cd106d7f2540728f46e6f8e0a9e58
6
+ metadata.gz: 0fc21e8b91f1f7f44404163478533ffcdffc4c42455bdd0f81881758c1a5faa12e49193d500869d22cf52a416fc2ce4a5a38ad71d5f6552194f265fdbb4649dc
7
+ data.tar.gz: 3e78c29a78d14c2ca7e0ce2c5cab4c914ae02b2a39a13feb337726e859ba6de0a56cb42bab28c18c602f42c7b0bc828ce6d11c77b7a142e1135622caf23054fe
data/README.md CHANGED
@@ -2,7 +2,7 @@ Feidee Utils
2
2
  ============
3
3
  [![Build Status](https://travis-ci.org/muyiliqing/feidee_utils.svg?branch=master)](https://travis-ci.org/muyiliqing/feidee_utils)
4
4
 
5
- Free users' data from [Feidee](http://www.feidee.com) private backups (.kbf).
5
+ Free user data from [Feidee](http://www.feidee.com) private backups (.kbf).
6
6
 
7
7
  Feidee And The KBF Format
8
8
  -----------
@@ -26,9 +26,9 @@ A set of ActiveRecord-like classes are provided to access the information in the
26
26
  require 'feidee_utils'
27
27
 
28
28
  kbf = FeideeUtils::Kbf.open_file(path_to_kbf_file)
29
- database = kbf.sqlite_db
30
- all_accounts = database.namespaced::Account.all
31
- all_transactions = database.namespaced::Transaction.all
29
+ database = kbf.db
30
+ all_accounts = database.ledger::Account.all
31
+ all_transactions = database.ledger::Transaction.all
32
32
  ```
33
33
 
34
34
  For more examples see ```examples/``` (To be added).
@@ -5,17 +5,34 @@ require 'bigdecimal'
5
5
  module FeideeUtils
6
6
  class Account < Record
7
7
  def validate_integrity
8
- raise "Account type should always be 0, but it's #{field["type"]}.\n" + inspect unless not field["type"] or field["type"] == 0
9
- raise "Account usedCount should always be 0, but it's #{field["usedCount"]}.\n" + inspect unless field["usedCount"] == 0
10
- raise "Account uuid should always be empty, but it's #{field["uuid"]}.\n" + inspect unless field["uuid"].to_s.empty?
11
- raise "Account hierachy contains more than 2 levels.\n" + inspect unless flat_parent_hierachy?
12
- raise "Account hidden should be either 0 or 1, but it's #{raw_hidden}.\n" + inspect unless (raw_hidden == 1 or raw_hidden == 0)
8
+ unless not field["type"] or field["type"] == 0
9
+ raise "Account type should always be 0, but it's #{field["type"]}.\n" +
10
+ inspect
11
+ end
12
+ unless field["usedCount"] == 0
13
+ raise "Account usedCount should always be 0," +
14
+ " but it's #{field["usedCount"]}.\n"+
15
+ inspect
16
+ end
17
+ unless field["uuid"].to_s.empty?
18
+ raise "Account uuid should always be empty,"+
19
+ " but it's #{field["uuid"]}.\n" +
20
+ inspect
21
+ end
22
+ unless flat_parent_hierachy?
23
+ raise "Account hierachy contains more than 2 levels.\n" + inspect
24
+ end
25
+ unless (raw_hidden == 1 or raw_hidden == 0)
26
+ raise "Account hidden should be either 0 or 1," +
27
+ " but it's #{raw_hidden}.\n" +
28
+ inspect
29
+ end
13
30
  end
14
31
 
15
32
  def self.validate_global_integrity
16
33
  if self.find_by_id(-1) != nil
17
- raise "-1 is used as the parent POID placeholder of a parent account. " +
18
- "Account of POID -1 should not exist."
34
+ raise "-1 is used as the parent POID placeholder of a parent account." +
35
+ " Account of POID -1 should not exist."
19
36
  end
20
37
  end
21
38
 
@@ -36,15 +53,12 @@ module FeideeUtils
36
53
  }.freeze
37
54
 
38
55
  IgnoredFields = [
39
- "tradingEntityPOID",
40
- # TODO: Field type is removed from database_version 73. Not sure about
41
- # earlier versions.
42
- "type", # Always 0
56
+ "tradingEntityPOID", # Foreign key to t_user or maybe t_tradingEntity.
57
+ "type", # Always 0, removed since database version 73.
43
58
  "usedCount", # Always 0
44
59
  "uuid", # Always empty.
45
- # TODO: code is also removed.
46
- "code", # WTF
47
- "clientID", # WTF
60
+ "code", # Always 0, removed since database version 73.
61
+ "clientID", # Always equal to poid.
48
62
  ].freeze
49
63
 
50
64
  define_accessors(FieldMappings)
@@ -96,7 +110,9 @@ module FeideeUtils
96
110
 
97
111
  def children
98
112
  arr = []
99
- self.class.database.query("SELECT * FROM #{self.class.table_name} WHERE parent = ?", poid) do |result|
113
+ self.class.database.query(
114
+ "SELECT * FROM #{self.class.table_name} WHERE parent = ?", poid
115
+ ) do |result|
100
116
  result.each do |raw_row|
101
117
  arr << self.class.new(result.columns, result.types, raw_row)
102
118
  end
@@ -104,6 +120,10 @@ module FeideeUtils
104
120
  arr
105
121
  end
106
122
 
123
+ def to_s
124
+ "#{name} (Account/#{poid})"
125
+ end
126
+
107
127
  class ModifiedAccount < Record::ModifiedRecord
108
128
  define_custom_methods([
109
129
  :balance,
@@ -26,9 +26,9 @@ module FeideeUtils
26
26
  }.freeze
27
27
 
28
28
  IgnoredFields = [
29
- "userTradingEntityPOID", # WTF
29
+ "userTradingEntityPOID", # Foreign key to t_user.
30
30
  "_tempIconName", # Icon name in the app
31
- "clientID", # WTF
31
+ "clientID", # Always equal to poid.
32
32
  ].freeze
33
33
 
34
34
  define_accessors(FieldMappings)
@@ -39,6 +39,10 @@ module FeideeUtils
39
39
  2 => :claim,
40
40
  })
41
41
 
42
+ def to_s
43
+ "#{name} (AccountGroup/#{poid})"
44
+ end
45
+
42
46
  # Schema
43
47
  # accountGroupPOID long not null
44
48
  # name varchar(100) not null
@@ -10,13 +10,18 @@ module FeideeUtils
10
10
  def validate_integrity
11
11
  validate_depth_integrity
12
12
  validate_one_level_path_integrity
13
- raise "Category usedCount should always be 0, but it's #{field["usedCount"]}.\n" + inspect unless field["usedCount"] == 0
13
+ unless field["usedCount"] == 0
14
+ raise "Category usedCount should always be 0, " +
15
+ "but it's #{field["usedCount"]}.\n" +
16
+ inspect
17
+ end
14
18
  end
15
19
 
16
20
  def self.validate_global_integrity
17
21
  project_root_code = 2
18
22
  if TypeEnum[project_root_code] != :project_root
19
- raise "The type code of project root has been changed, please update the code."
23
+ raise "The type code of project root has been changed," +
24
+ " please update the code."
20
25
  end
21
26
 
22
27
  rows = self.database.execute <<-SQL
@@ -26,11 +31,13 @@ module FeideeUtils
26
31
 
27
32
  if rows.length > 1
28
33
  poids = rows.map do |row| row[0] end
29
- raise "More than one category have type project_root. IDs are #{poids.inspect}."
34
+ raise "More than one category have type project_root." +
35
+ " IDs are #{poids.inspect}."
30
36
  elsif rows.length == 1
31
37
  category_name = rows[0][1]
32
38
  if category_name != "projectRoot" and category_name != "root"
33
- raise "Category #{category_name} has type project_root. ID: #{rows[0][0]}."
39
+ raise "Category #{category_name} has type project_root." +
40
+ " ID: #{rows[0][0]}."
34
41
  end
35
42
  end
36
43
  end
@@ -45,10 +52,10 @@ module FeideeUtils
45
52
  }.freeze
46
53
 
47
54
  IgnoredFields = [
48
- "userTradingEntityPOID", # WTF
55
+ "userTradingEntityPOID", # Foreign key to t_user.
49
56
  "_tempIconName", # Icon name in the app
50
57
  "usedCount", # Always 0.
51
- "clientID", # WTF
58
+ "clientID", # Always equal to poid.
52
59
  ].freeze
53
60
 
54
61
  define_accessors(FieldMappings)
@@ -59,6 +66,10 @@ module FeideeUtils
59
66
  2 => :project_root, # unkown
60
67
  })
61
68
 
69
+ def to_s
70
+ "#{name} (Category/#{poid})"
71
+ end
72
+
62
73
  # Schema
63
74
  # categoryPOID LONG NOT NULL
64
75
  # name varchar(100) NOT NULL
@@ -22,12 +22,14 @@ module FeideeUtils
22
22
  t_fund_trans
23
23
  t_module_stock_holding
24
24
  t_module_stock_tran
25
+ t_tradingEntity
26
+ t_user
25
27
  ).freeze
26
28
 
27
29
  attr_reader :sqlite_file
28
- attr_reader :platform, :sqlite_name, :sqlite_timestamp
30
+ attr_reader :platform, :ledger_name, :last_modified_at
29
31
  attr_reader :missing_tables
30
- attr_reader :namespaced
32
+ attr_reader :ledger
31
33
 
32
34
  def initialize(private_sqlite, strip = false)
33
35
  @sqlite_file = Database.feidee_to_sqlite(private_sqlite)
@@ -37,7 +39,8 @@ module FeideeUtils
37
39
  extract_metadata
38
40
  drop_unused_tables if strip
39
41
 
40
- @namespaced = Record.generate_namespaced_record_classes(self)
42
+ # TODO: make Ledger a first class object.
43
+ @ledger = Record.generate_namespaced_record_classes(self)
41
44
  end
42
45
 
43
46
  def sqlite_backup(dest_file_path)
@@ -51,8 +54,8 @@ module FeideeUtils
51
54
  end
52
55
 
53
56
  def validate_global_integrity
54
- @namespaced.constants.each do |const|
55
- @namespaced.const_get(const).validate_global_integrity if const != :Database
57
+ @ledger.constants.each do |const|
58
+ @ledger.const_get(const).validate_global_integrity if const != :Database
56
59
  end
57
60
  end
58
61
 
@@ -71,7 +74,10 @@ module FeideeUtils
71
74
 
72
75
  def drop_unused_tables
73
76
  useful_tables = Tables.values + PotentialUsefulTables
74
- useful_tables = (useful_tables + useful_tables.map do |x| self.class.trash_table_name(x) end).sort
77
+ useful_tables += useful_tables.map do |x|
78
+ self.class.trash_table_name(x)
79
+ end
80
+ useful_tables.sort!
75
81
 
76
82
  # TODO: Record all tables droped.
77
83
  (all_tables - useful_tables).each do |table|
@@ -85,14 +91,20 @@ module FeideeUtils
85
91
  end
86
92
 
87
93
  def extract_metadata
88
- @platform = self.execute("SELECT platform from #{Tables[:metadata]}")[0][0];
89
-
90
- @sqlite_name = self.get_first_row("SELECT accountBookName FROM #{Tables[:profile]};")[0];
91
-
92
- # This is not recorded in the database, so the lastest lastUpdateTime of all
93
- # transactions is chosen.
94
- timestamp = self.get_first_row("SELECT max(lastUpdateTime) FROM #{Tables[:transactions]};")[0]
95
- @sqlite_timestamp = timestamp == nil ? Time.at(0) : Time.at(timestamp / 1000)
94
+ @platform = self.execute(
95
+ "SELECT platform from #{Tables[:metadata]}"
96
+ )[0][0];
97
+
98
+ @ledger_name = self.get_first_row(
99
+ "SELECT accountBookName FROM #{Tables[:profile]};"
100
+ )[0];
101
+
102
+ # This is not recorded in the database, so the lastest lastUpdateTime of
103
+ # all transactions is chosen.
104
+ timestamp = self.get_first_row(
105
+ "SELECT max(lastUpdateTime) FROM #{Tables[:transactions]};"
106
+ )[0]
107
+ @last_modified_at = Time.at((timestamp or 0.0) / 1000)
96
108
  end
97
109
 
98
110
  class << self
@@ -103,13 +115,14 @@ module FeideeUtils
103
115
  Header = "SQLite format 3\0".force_encoding("binary")
104
116
  FeideeHeader_iOS = "%$^#&!@_@- -!F\xff\0".force_encoding('binary')
105
117
  FeideeHeader_Android = ("\0" * 13 + "F\xff\0").force_encoding("binary")
118
+ AllHeaders = [FeideeHeader_iOS, FeideeHeader_Android, Header]
106
119
 
107
120
  def feidee_to_sqlite(private_sqlite, sqlite_file = nil)
108
121
  # Discard the first a few bytes content.
109
122
  private_header = private_sqlite.read(Header.length)
110
123
 
111
- unless [FeideeHeader_iOS, FeideeHeader_Android, Header].include? private_header
112
- raise "Unexpected header #{private_header.inspect} in private sqlite file."
124
+ unless AllHeaders.include? private_header
125
+ raise "Unexpected private sqlite header #{private_header.inspect}."
113
126
  end
114
127
 
115
128
  # Write the rest to a tempfile.
@@ -123,7 +136,10 @@ module FeideeUtils
123
136
  end
124
137
 
125
138
  class << self
126
- NoDeleteSuffixTables = %w(account category tag tradingEntity transaction transaction_template)
139
+ NoDeleteSuffixTables = %w(
140
+ account category tag tradingEntity
141
+ transaction transaction_template
142
+ )
127
143
 
128
144
  def trash_table_name name
129
145
  NoDeleteSuffixTables.each do |core_name|
@@ -9,22 +9,25 @@ module FeideeUtils
9
9
  class Kbf
10
10
  DatabaseName = 'mymoney.sqlite'
11
11
 
12
- attr_reader :zipfile, :sqlite_db
12
+ attr_reader :zipfile, :db
13
13
 
14
14
  def initialize(input_stream)
15
- @zipfile = Zip::File.open_buffer(input_stream) do |zipfile|
15
+ Zip::File.open_buffer(input_stream) do |zipfile|
16
16
  zipfile.each do |entry|
17
17
  if entry.name == DatabaseName
18
18
  # Each call to get_input_stream will create a new stream
19
19
  @original_sqlite_db_entry = entry
20
- @sqlite_db = FeideeUtils::Database.new(entry.get_input_stream, true)
20
+ @db = FeideeUtils::Database.new(entry.get_input_stream, true)
21
21
  end
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  def extract_original_sqlite(dest_file_path = nil)
27
- FeideeUtils::Database.feidee_to_sqlite(@original_sqlite_db_entry.get_input_stream, dest_file_path)
27
+ FeideeUtils::Database.feidee_to_sqlite(
28
+ @original_sqlite_db_entry.get_input_stream,
29
+ dest_file_path
30
+ )
28
31
  end
29
32
 
30
33
  class << self
@@ -12,7 +12,9 @@ module FeideeUtils
12
12
  module ClassMethods
13
13
  def define_accessors field_mappings
14
14
  field_mappings.each do |name, key|
15
- raise "Accessor #{name} already exists in #{self.name}." if method_defined? name
15
+ if method_defined? name
16
+ raise "Accessor #{name} already exists in #{self.name}."
17
+ end
16
18
  define_method name do field[key] end
17
19
  end
18
20
  end
@@ -12,7 +12,8 @@ module FeideeUtils
12
12
  @child_classes.add(child_class)
13
13
  end
14
14
 
15
- # To use Record with different databases, generate a set of classes for each db
15
+ # To use Record with different databases, generate a set of classes for
16
+ # each db
16
17
  def generate_namespaced_record_classes(db)
17
18
  @child_classes ||= Set.new
18
19
  this = self
@@ -36,13 +36,17 @@ module FeideeUtils
36
36
  end
37
37
 
38
38
  def find_by_id(id)
39
- raw_result = database.query("SELECT * FROM #{self.table_name} WHERE #{self.id_field_name} = ?", id)
39
+ raw_result = database.query(
40
+ "SELECT * FROM #{self.table_name} WHERE #{self.id_field_name} = ?",
41
+ id
42
+ )
40
43
 
41
44
  raw_row = raw_result.next
42
45
  return nil if raw_row == nil
43
46
 
44
47
  if raw_result.next != nil
45
- raise "Getting more than one result with the same ID #{id} in table #{self.table_name}."
48
+ raise "Getting more than one result with the same ID #{id} " +
49
+ "in table #{self.table_name}."
46
50
  end
47
51
 
48
52
  self.new(raw_result.columns, raw_result.types, raw_row)
@@ -6,18 +6,21 @@ require 'feidee_utils/record/modified_record'
6
6
 
7
7
  module FeideeUtils
8
8
  # The implementation here is wired.
9
- # The goal is to create a class hierachy similar to ActiveRecord, where every table is represented by a
10
- # subclass of ActiveRecord::Base. Class methods, attribute accessors and almost all other functionalities
11
- # are provided by ActiveRecord::Base. For example, Base.all(), Base.find_by_id() are tied to a specific
12
- # table in a specific database.
13
- # The problem we are solving here is not the same as ActiveRecord. In ActiveRecord, the databases
14
- # are static, i.e. they won't be changed at runtime. Meanwhile, in our case, new databases can be created
15
- # at runtime, when a new KBF backup file is uploaded. Furthermore, multiple instances of different databases
16
- # can co-exist at the same time. To provide the same syntax as ActiveRecord, a standalone "Base" class has
17
- # to be created for each database.
18
- # In our implementation, when a new database is created, a subclass of Record is created in a new namepsace.
19
- # For each subclass of Record, a new subclass is copied to the new namespace, with it's database method
20
- # overloaded.
9
+ # The goal is to create a class hierachy similar to ActiveRecord, where every
10
+ # table is represented by a subclass of ActiveRecord::Base. Class methods,
11
+ # attribute accessors and almost all other functionalities are provided by
12
+ # ActiveRecord::Base. For example, Base.all(), Base.find_by_id() are tied to
13
+ # a specific table in a specific database.
14
+ # The problem we are solving here is not the same as ActiveRecord. In
15
+ # ActiveRecord, the databases are static, i.e. they won't be changed at
16
+ # runtime. Meanwhile, in our case, new databases can be created at runtime,
17
+ # when a new KBF backup file is uploaded. Furthermore, multiple instances of
18
+ # different databases can co-exist at the same time. To provide the same
19
+ # syntax as ActiveRecord, a standalone "Base" class has to be created for each
20
+ # database.
21
+ # In our implementation, when a new database is created, a subclass of Record
22
+ # is created in a new namepsace. For each subclass of Record, a new subclass
23
+ # is copied to the new namespace, with it's database method overloaded.
21
24
  class Record
22
25
  attr_reader :field, :field_type
23
26
 
@@ -25,30 +25,37 @@ module FeideeUtils
25
25
  unless buyer_account_poid != 0 and seller_account_poid != 0
26
26
  raise TransferLackBuyerOrSellerException,
27
27
  "Both buyer and seller should be set in a transfer. " +
28
- "Buyer account POID: #{buyer_account_poid}. Seller account POID: #{seller_account_poid}.\n" +
28
+ "Buyer account POID: #{buyer_account_poid}. " +
29
+ "Seller account POID: #{seller_account_poid}.\n" +
29
30
  inspect
30
31
  end
31
32
  unless buyer_category_poid == 0 and seller_category_poid == 0
32
33
  raise TransferWithCategoryException,
33
34
  "Neither buyer or seller category should be set in a transfer. " +
34
- "Buyer category POID: #{buyer_category_poid}. Seller category POID: #{seller_category_poid}.\n" +
35
+ "Buyer category POID: #{buyer_category_poid}. " +
36
+ "Seller category POID: #{seller_category_poid}.\n" +
35
37
  inspect
36
38
  end
37
39
  else
38
40
  unless (buyer_account_poid == 0) ^ (seller_account_poid == 0)
39
41
  raise InconsistentBuyerAndSellerException,
40
- "Exactly one of buyer and seller should be set in a non-transfer transaction. " +
41
- "Buyer account POID: #{buyer_account_poid}. Seller account POID: #{seller_account_poid}.\n" +
42
+ "Exactly one of buyer and seller should be set in a non-transfer " +
43
+ "transaction. " +
44
+ "Buyer account POID: #{buyer_account_poid}. " +
45
+ "Seller account POID: #{seller_account_poid}.\n" +
42
46
  inspect
43
47
  end
44
48
 
45
- # We could enforce that category is set to the matching party (buyer or seller) of account.
46
- # However the implementation could handle all situations, as long as only one of them is set.
47
- # Thus no extra check is done here.
49
+ # We could enforce that category is set to the matching party (buyer or
50
+ # seller) of account. However the implementation could handle all
51
+ # situations, as long as only one of them is set. Thus no extra check is
52
+ # done here.
48
53
  unless buyer_category_poid == 0 or seller_category_poid == 0
49
54
  raise InconsistentCategoryException,
50
- "Only one of buyer and seller category should be set in a non-transfer transaction. "
51
- "Buyer category POID: #{buyer_category_poid}. Seller category POID: #{seller_category_poid}.\n" +
55
+ "Only one of buyer and seller category should be set in a " +
56
+ "non-transfer transaction. " +
57
+ "Buyer category POID: #{buyer_category_poid}. " +
58
+ "Seller category POID: #{seller_category_poid}.\n" +
52
59
  inspect
53
60
  end
54
61
  end
@@ -56,7 +63,8 @@ module FeideeUtils
56
63
  unless raw_buyer_deduction == raw_seller_addition
57
64
  raise InconsistentAmountException,
58
65
  "Buyer and seller should have the same amount set. " +
59
- "Buyer deduction: #{buyer_deduction}, seller_addition: #{seller_addition}.\n" +
66
+ "Buyer deduction: #{buyer_deduction}. "+
67
+ "Seller_addition: #{seller_addition}.\n" +
60
68
  inspect
61
69
  end
62
70
  end
@@ -78,8 +86,10 @@ module FeideeUtils
78
86
  valid = true
79
87
  valid &&= transfers[0] != nil
80
88
  valid &&= transfers[1] != nil
81
- valid &&= transfers[0].buyer_account_poid == transfers[1].buyer_account_poid
82
- valid &&= transfers[0].seller_account_poid == transfers[1].seller_account_poid
89
+ valid &&=
90
+ transfers[0].buyer_account_poid == transfers[1].buyer_account_poid
91
+ valid &&=
92
+ transfers[0].seller_account_poid == transfers[1].seller_account_poid
83
93
  raise TransfersNotPaired.new([uuid] + transfers) unless valid
84
94
  end
85
95
  end
@@ -102,13 +112,13 @@ module FeideeUtils
102
112
  define_entity_accessor :seller_account_poid, :account
103
113
 
104
114
  IgnoredFields = [
105
- "creatorTradingEntityPOID",
106
- "modifierTradingEntityPOID",
107
- "ffrom", # The signature of the App writting this transaction.
115
+ "creatorTradingEntityPOID", # Foreign key to to_user.
116
+ "modifierTradingEntityPOID", # Foreign key to to_user.
117
+ "ffrom", # The signature of the App.
108
118
  "photoName", # To be added
109
119
  "photoNeedUpload", # To be added
110
- "relationUnitPOID", # WTF
111
- "clientID", # WTF
120
+ "relationUnitPOID", # Foreign key to t_tradingEntity: Merchant.
121
+ "clientID", # Always 0 for ealier versions, or equal to poid.
112
122
  "FSourceKey", # WTF
113
123
  ].freeze
114
124
 
@@ -190,6 +200,17 @@ module FeideeUtils
190
200
  end
191
201
  end
192
202
 
203
+ def to_s
204
+ if is_transfer?
205
+ "Transfer #{amount.to_f} from #{buyer_account} to #{seller_account}"
206
+ elsif is_initial_balance?
207
+ "Balance of #{revised_account} set to #{revised_amount.to_f}"
208
+ else
209
+ "Transaction of #{revised_amount.to_f} on #{revised_account} in " +
210
+ "#{category}"
211
+ end
212
+ end
213
+
193
214
  class ModifiedTransaction < ModifiedRecord
194
215
  define_custom_methods([
195
216
  :created_at,
@@ -1,3 +1,3 @@
1
1
  module FeideeUtils
2
- VERSION = '0.0.4.3'
2
+ VERSION = '0.0.4.4'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feidee_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4.3
4
+ version: 0.0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liqing Muyi