invoicing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/LICENSE +20 -0
- data/Manifest +60 -0
- data/README +48 -0
- data/Rakefile +75 -0
- data/invoicing.gemspec +41 -0
- data/lib/invoicing.rb +9 -0
- data/lib/invoicing/cached_record.rb +107 -0
- data/lib/invoicing/class_info.rb +187 -0
- data/lib/invoicing/connection_adapter_ext.rb +44 -0
- data/lib/invoicing/countries/uk.rb +24 -0
- data/lib/invoicing/currency_value.rb +212 -0
- data/lib/invoicing/find_subclasses.rb +193 -0
- data/lib/invoicing/ledger_item.rb +718 -0
- data/lib/invoicing/ledger_item/render_html.rb +515 -0
- data/lib/invoicing/ledger_item/render_ubl.rb +268 -0
- data/lib/invoicing/line_item.rb +246 -0
- data/lib/invoicing/price.rb +9 -0
- data/lib/invoicing/tax_rate.rb +9 -0
- data/lib/invoicing/taxable.rb +355 -0
- data/lib/invoicing/time_dependent.rb +388 -0
- data/lib/invoicing/version.rb +21 -0
- data/test/cached_record_test.rb +100 -0
- data/test/class_info_test.rb +253 -0
- data/test/connection_adapter_ext_test.rb +71 -0
- data/test/currency_value_test.rb +184 -0
- data/test/find_subclasses_test.rb +120 -0
- data/test/fixtures/README +7 -0
- data/test/fixtures/cached_record.sql +22 -0
- data/test/fixtures/class_info.sql +28 -0
- data/test/fixtures/currency_value.sql +29 -0
- data/test/fixtures/find_subclasses.sql +43 -0
- data/test/fixtures/ledger_item.sql +39 -0
- data/test/fixtures/line_item.sql +33 -0
- data/test/fixtures/price.sql +4 -0
- data/test/fixtures/tax_rate.sql +4 -0
- data/test/fixtures/taxable.sql +14 -0
- data/test/fixtures/time_dependent.sql +35 -0
- data/test/ledger_item_test.rb +352 -0
- data/test/line_item_test.rb +139 -0
- data/test/models/README +4 -0
- data/test/models/test_subclass_in_another_file.rb +3 -0
- data/test/models/test_subclass_not_in_database.rb +6 -0
- data/test/price_test.rb +9 -0
- data/test/ref-output/creditnote3.html +82 -0
- data/test/ref-output/creditnote3.xml +89 -0
- data/test/ref-output/invoice1.html +93 -0
- data/test/ref-output/invoice1.xml +111 -0
- data/test/ref-output/invoice2.html +86 -0
- data/test/ref-output/invoice2.xml +98 -0
- data/test/ref-output/invoice_null.html +36 -0
- data/test/render_html_test.rb +69 -0
- data/test/render_ubl_test.rb +32 -0
- data/test/setup.rb +37 -0
- data/test/tax_rate_test.rb +9 -0
- data/test/taxable_test.rb +180 -0
- data/test/test_helper.rb +48 -0
- data/test/time_dependent_test.rb +180 -0
- data/website/curvycorners.js +1 -0
- data/website/screen.css +149 -0
- data/website/template.html.erb +43 -0
- metadata +180 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Invoicing
|
2
|
+
module Version
|
3
|
+
MAJOR = 0
|
4
|
+
MINOR = 1
|
5
|
+
BUILD = 0
|
6
|
+
|
7
|
+
def to_a
|
8
|
+
[MAJOR, MINOR, BUILD]
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
to_a.join(".")
|
13
|
+
end
|
14
|
+
|
15
|
+
module_function :to_a
|
16
|
+
module_function :to_s
|
17
|
+
|
18
|
+
STRING = Version.to_s
|
19
|
+
end
|
20
|
+
VERSION = Version.to_s
|
21
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
class CachedRecordTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
class CachedRecord < ActiveRecord::Base
|
6
|
+
set_primary_key 'id2'
|
7
|
+
acts_as_cached_record :id => 'id2'
|
8
|
+
has_many :referrers, :class_name => 'RefersToCachedRecord', :foreign_key => 'cached_record_id'
|
9
|
+
end
|
10
|
+
|
11
|
+
class RefersToCachedRecord < ActiveRecord::Base
|
12
|
+
belongs_to :cached_record
|
13
|
+
end
|
14
|
+
|
15
|
+
class CachedRecordMockDatabase < ActiveRecord::Base
|
16
|
+
set_table_name 'cached_records'
|
17
|
+
set_primary_key 'id2'
|
18
|
+
acts_as_cached_record :id => 'id2'
|
19
|
+
|
20
|
+
def self.connection
|
21
|
+
@connection_mock ||= FlexMock.new('connection')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def test_find_with_valid_id_should_return_record
|
27
|
+
record = CachedRecord.find(1)
|
28
|
+
assert_not_nil record
|
29
|
+
assert record.kind_of?(CachedRecord)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_find_with_invalid_id_should_raise_exception
|
33
|
+
assert_raise ActiveRecord::RecordNotFound do
|
34
|
+
CachedRecord.find(99)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_find_with_valid_id_should_not_access_database
|
39
|
+
CachedRecordMockDatabase.connection.should_receive(:select).and_throw('should not access database')
|
40
|
+
assert_not_nil CachedRecordMockDatabase.find(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_find_with_invalid_id_should_not_access_database
|
44
|
+
CachedRecordMockDatabase.connection.should_receive(:select).and_throw('should not access database')
|
45
|
+
assert_raise ActiveRecord::RecordNotFound do
|
46
|
+
CachedRecordMockDatabase.find(99)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_find_with_conditions_should_still_work
|
51
|
+
assert_equal CachedRecord.find_by_value('Two'), CachedRecord.find(2)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_find_with_conditions_should_not_use_the_cache
|
55
|
+
assert !CachedRecord.find_by_value('Two').equal?(CachedRecord.find(2))
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_find_without_ids_should_raise_exception
|
59
|
+
assert_raise ActiveRecord::RecordNotFound do
|
60
|
+
CachedRecord.find
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_find_with_empty_list_of_ids_should_raise_exception
|
65
|
+
assert_raise ActiveRecord::RecordNotFound do
|
66
|
+
CachedRecord.find(:conditions => {:id => []})
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_find_with_list_of_ids_should_return_list_of_objects
|
71
|
+
expected = CachedRecord.cached_record_list.sort{|r1, r2| r1.id - r2.id}
|
72
|
+
assert_equal expected, CachedRecord.find([1,2])
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_cached_record_associations_should_still_work
|
76
|
+
assert_equal 2, CachedRecord.find(1).referrers.length
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_foreign_key_to_cached_record_should_use_cache
|
80
|
+
assert RefersToCachedRecord.find(1).cached_record.equal?(CachedRecord.find(1))
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_cached_record_list_should_return_all_objects
|
84
|
+
assert_equal 2, CachedRecord.cached_record_list.length
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_cached_record_list_should_not_access_database
|
88
|
+
CachedRecordMockDatabase.connection.should_receive(:select).and_throw('should not access database')
|
89
|
+
assert_not_nil CachedRecordMockDatabase.cached_record_list
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_reload_cache_should_do_what_it_says_on_the_tin
|
93
|
+
CachedRecord.connection.execute "insert into cached_records (id2, value) values(3, 'Three')"
|
94
|
+
CachedRecord.reload_cache
|
95
|
+
record = CachedRecord.find(3)
|
96
|
+
assert_not_nil record
|
97
|
+
assert record.kind_of?(CachedRecord)
|
98
|
+
assert_equal 3, CachedRecord.cached_record_list.length
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
# Mini implementation of the ClassInfo pattern, at which we can fire our tests
|
4
|
+
|
5
|
+
module MyNamespace
|
6
|
+
module ClassInfoTestModule
|
7
|
+
module ActMethods
|
8
|
+
def acts_as_class_info_test(*args)
|
9
|
+
Invoicing::ClassInfo.acts_as(MyNamespace::ClassInfoTestModule, self, args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_class_info
|
14
|
+
class_info_test_module_class_info
|
15
|
+
end
|
16
|
+
|
17
|
+
def my_instance_method
|
18
|
+
class_info_test_module_class_info.the_answer / value
|
19
|
+
end
|
20
|
+
|
21
|
+
def my_other_instance_method
|
22
|
+
class_info_test_module_class_info.not_the_answer / value
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def my_class_method(number)
|
27
|
+
class_info_test_module_class_info.the_answer / number
|
28
|
+
end
|
29
|
+
|
30
|
+
def my_other_class_method(number)
|
31
|
+
class_info_test_module_class_info.not_the_answer / number
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_class_info
|
35
|
+
class_info_test_module_class_info
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ClassInfo < Invoicing::ClassInfo::Base
|
40
|
+
def foo
|
41
|
+
'foo'
|
42
|
+
end
|
43
|
+
|
44
|
+
def the_answer
|
45
|
+
all_args.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def not_the_answer
|
49
|
+
all_args.last
|
50
|
+
end
|
51
|
+
|
52
|
+
def option_defaults
|
53
|
+
{:option1 => :baa, :option2 => :blah}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
module ClassInfoTest2
|
60
|
+
module ActMethods
|
61
|
+
def acts_as_class_info_test2(*args)
|
62
|
+
Invoicing::ClassInfo.acts_as(MyNamespace::ClassInfoTest2, self, args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
def test2_class_info
|
68
|
+
class_info_test2_class_info
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ClassInfo < Invoicing::ClassInfo::Base
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
ActiveRecord::Base.send(:extend, MyNamespace::ClassInfoTestModule::ActMethods)
|
78
|
+
ActiveRecord::Base.send(:extend, MyNamespace::ClassInfoTest2::ActMethods)
|
79
|
+
|
80
|
+
|
81
|
+
# Model objects which use the acts_as feature defined above
|
82
|
+
|
83
|
+
class ClassInfoTestRecord < ActiveRecord::Base
|
84
|
+
acts_as_class_info_test 42, :option1 => :moo
|
85
|
+
acts_as_class_info_test 84, 42, 168, :option3 => :asdf
|
86
|
+
|
87
|
+
def self.class_foo
|
88
|
+
class_info_test_module_class_info.foo
|
89
|
+
end
|
90
|
+
|
91
|
+
def instance_foo
|
92
|
+
class_info_test_module_class_info.foo
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class ClassInfoTestSubclass < ClassInfoTestRecord
|
97
|
+
acts_as_class_info_test 336, :option1 => :quack, :option4 => :fdsa
|
98
|
+
end
|
99
|
+
|
100
|
+
class ClassInfoTestSubclass2 < ClassInfoTestRecord
|
101
|
+
acts_as_class_info_test2 :option1 => :badger
|
102
|
+
end
|
103
|
+
|
104
|
+
class ClassInfoTestSubSubclass < ClassInfoTestSubclass2
|
105
|
+
acts_as_class_info_test 112, :option3 => 1234
|
106
|
+
end
|
107
|
+
|
108
|
+
class ClassInfoTestEmptySubclass < ClassInfoTestRecord
|
109
|
+
end
|
110
|
+
|
111
|
+
class ClassInfoTest2Record < ActiveRecord::Base
|
112
|
+
acts_as_class_info_test2 :option1 => :okapi, :option3 => :kangaroo
|
113
|
+
|
114
|
+
def option1; 'this is option1'; end
|
115
|
+
def option2; 'this is option2'; end
|
116
|
+
def kangaroo; 'bounce'; end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
#######################################################################################
|
121
|
+
|
122
|
+
class ClassInfoTest < Test::Unit::TestCase
|
123
|
+
|
124
|
+
def test_call_into_class_info_via_class
|
125
|
+
assert_equal 'foo', ClassInfoTestRecord.class_foo
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_call_into_class_info_via_instance
|
129
|
+
assert_equal 'foo', ClassInfoTestRecord.new.instance_foo
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_mixin_superclass_instance_methods
|
133
|
+
assert_equal 21, ClassInfoTestRecord.find(1).my_instance_method
|
134
|
+
assert_equal 84, ClassInfoTestRecord.find(1).my_other_instance_method
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_mixin_superclass_class_methods
|
138
|
+
assert_equal 14, ClassInfoTestRecord.my_class_method(3)
|
139
|
+
assert_equal 28, ClassInfoTestRecord.my_other_class_method(6)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_mixin_subclass_instance_methods
|
143
|
+
assert_equal 14, ClassInfoTestRecord.find(2).my_instance_method
|
144
|
+
assert_equal 112, ClassInfoTestRecord.find(2).my_other_instance_method
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_mixin_subclass_class_methods
|
148
|
+
assert_equal 14, ClassInfoTestSubclass.my_class_method(3)
|
149
|
+
assert_equal 56, ClassInfoTestSubclass.my_other_class_method(6)
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_all_args_in_superclass
|
153
|
+
assert_equal [42, 84, 168], ClassInfoTestRecord.get_class_info.all_args
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_all_args_in_subclass
|
157
|
+
assert_equal [42, 84, 168, 336], ClassInfoTestSubclass.get_class_info.all_args
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_all_args_in_sub_subclass
|
161
|
+
assert_equal [42, 84, 168, 112], ClassInfoTestSubSubclass.get_class_info.all_args
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_current_args_in_superclass
|
165
|
+
assert_equal [84, 42, 168], ClassInfoTestRecord.get_class_info.current_args
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_current_args_in_subclass
|
169
|
+
assert_equal [336], ClassInfoTestSubclass.get_class_info.current_args
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_current_args_in_sub_subclass
|
173
|
+
assert_equal [112], ClassInfoTestSubSubclass.get_class_info.current_args
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_new_args_in_superclass
|
177
|
+
assert_equal [84, 168], ClassInfoTestRecord.get_class_info.new_args
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_new_args_in_subclass
|
181
|
+
assert_equal [336], ClassInfoTestSubclass.get_class_info.new_args
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_new_args_in_sub_subclass
|
185
|
+
assert_equal [112], ClassInfoTestSubSubclass.get_class_info.new_args
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_all_options_in_superclass
|
189
|
+
assert_equal :moo, ClassInfoTestRecord.get_class_info.all_options[:option1]
|
190
|
+
assert_equal :blah, ClassInfoTestRecord.get_class_info.all_options[:option2]
|
191
|
+
assert_equal :asdf, ClassInfoTestRecord.get_class_info.all_options[:option3]
|
192
|
+
assert_nil ClassInfoTestRecord.get_class_info.all_options[:option4]
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_all_options_in_subclass
|
196
|
+
assert_equal :quack, ClassInfoTestSubclass.get_class_info.all_options[:option1]
|
197
|
+
assert_equal :blah, ClassInfoTestSubclass.get_class_info.all_options[:option2]
|
198
|
+
assert_equal :asdf, ClassInfoTestSubclass.get_class_info.all_options[:option3]
|
199
|
+
assert_equal :fdsa, ClassInfoTestSubclass.get_class_info.all_options[:option4]
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_all_options_in_sub_subclass
|
203
|
+
assert_equal :moo, ClassInfoTestSubSubclass.get_class_info.all_options[:option1]
|
204
|
+
assert_equal :blah, ClassInfoTestSubSubclass.get_class_info.all_options[:option2]
|
205
|
+
assert_equal 1234, ClassInfoTestSubSubclass.get_class_info.all_options[:option3]
|
206
|
+
assert_nil ClassInfoTestSubSubclass.get_class_info.all_options[:option4]
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_current_options_in_superclass
|
210
|
+
assert_equal({:option3 => :asdf}, ClassInfoTestRecord.get_class_info.current_options)
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_current_options_in_subclass
|
214
|
+
assert_equal({:option1 => :quack, :option4 => :fdsa}, ClassInfoTestSubclass.get_class_info.current_options)
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_two_features_in_the_same_model
|
218
|
+
assert_equal({:option1 => :badger}, ClassInfoTestSubclass2.test2_class_info.all_options)
|
219
|
+
assert_equal({:option1 => :badger}, ClassInfoTestSubSubclass.test2_class_info.all_options)
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_the_same_feature_in_two_models
|
223
|
+
assert_equal({:option1 => :okapi, :option3 => :kangaroo}, ClassInfoTest2Record.test2_class_info.all_options)
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_method_renamed
|
227
|
+
assert_equal 'kangaroo', ClassInfoTest2Record.test2_class_info.method(:option3)
|
228
|
+
assert_equal 'bounce', ClassInfoTest2Record.test2_class_info.get(ClassInfoTest2Record.find(1), :option3)
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_database_column_renamed
|
232
|
+
assert_equal 'okapi', ClassInfoTest2Record.test2_class_info.method(:option1)
|
233
|
+
assert_equal 'OKAPI!', ClassInfoTest2Record.test2_class_info.get(ClassInfoTest2Record.find(1), :option1)
|
234
|
+
end
|
235
|
+
|
236
|
+
def test_method_not_renamed
|
237
|
+
assert_equal 'option2', ClassInfoTest2Record.test2_class_info.method(:option2)
|
238
|
+
assert_equal 'this is option2', ClassInfoTest2Record.test2_class_info.get(ClassInfoTest2Record.find(1), :option2)
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_method_on_nil_object
|
242
|
+
assert_nil ClassInfoTest2Record.test2_class_info.get(nil, :option2)
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_method_not_found_via_class_info
|
246
|
+
assert_nil ClassInfoTest2Record.test2_class_info.get(ClassInfoTest2Record.find(1), :this_method_does_not_exist)
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_inherited_to_empty_subclass
|
250
|
+
assert_not_nil ClassInfoTestEmptySubclass.new.get_class_info
|
251
|
+
assert_equal ClassInfoTestEmptySubclass.new.get_class_info, ClassInfoTestRecord.new.get_class_info
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
class ConnectionAdapterExtTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
# Don't run these tests in database transactions.
|
6
|
+
def setup
|
7
|
+
end
|
8
|
+
def teardown
|
9
|
+
end
|
10
|
+
|
11
|
+
def using_database(database_type)
|
12
|
+
begin
|
13
|
+
ActiveRecord::Base.establish_connection(TEST_DB_CONFIG[database_type.to_sym])
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
connect_to_testing_database
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_conditional_function_as_mysql
|
21
|
+
using_database :mysql do
|
22
|
+
assert_equal "IF(true, foo, bar)", Invoicing::ConnectionAdapterExt.conditional_function('true', 'foo', 'bar')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_conditional_function_as_postgresql
|
27
|
+
using_database :postgresql do
|
28
|
+
assert_equal "CASE WHEN true THEN foo ELSE bar END",
|
29
|
+
Invoicing::ConnectionAdapterExt.conditional_function('true', 'foo', 'bar')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_conditional_function_as_sqlite3
|
34
|
+
using_database :sqlite3 do
|
35
|
+
assert_raise RuntimeError do
|
36
|
+
Invoicing::ConnectionAdapterExt.conditional_function('true', 'foo', 'bar')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_group_by_all_columns_as_mysql
|
42
|
+
using_database :mysql do
|
43
|
+
assert_equal "`ledger_item_records`.`id2`",
|
44
|
+
Invoicing::ConnectionAdapterExt.group_by_all_columns(MyLedgerItem)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_group_by_all_columns_as_postgresql
|
49
|
+
using_database :postgresql do
|
50
|
+
assert_equal(
|
51
|
+
'"ledger_item_records"."id2", "ledger_item_records"."type2", "ledger_item_records"."sender_id2", ' +
|
52
|
+
'"ledger_item_records"."recipient_id2", "ledger_item_records"."identifier2", ' +
|
53
|
+
'"ledger_item_records"."issue_date2", "ledger_item_records"."currency2", ' +
|
54
|
+
'"ledger_item_records"."total_amount2", "ledger_item_records"."tax_amount2", ' +
|
55
|
+
'"ledger_item_records"."status2", "ledger_item_records"."period_start2", ' +
|
56
|
+
'"ledger_item_records"."period_end2", "ledger_item_records"."uuid2", ' +
|
57
|
+
'"ledger_item_records"."due_date2", "ledger_item_records"."created_at", ' +
|
58
|
+
'"ledger_item_records"."updated_at"',
|
59
|
+
Invoicing::ConnectionAdapterExt.group_by_all_columns(MyLedgerItem))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_group_by_all_columns_as_sqlite3
|
64
|
+
using_database :sqlite3 do
|
65
|
+
assert_raise RuntimeError do
|
66
|
+
Invoicing::ConnectionAdapterExt.group_by_all_columns(MyLedgerItem)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
4
|
+
|
5
|
+
# Test extending the default list of currency codes: include the Zimbabwe Dollar.
|
6
|
+
# This also tests rounding and seriously large numbers. -- Sorry, you shouldn't make
|
7
|
+
# jokes about this sort of thing. The people are suffering badly.
|
8
|
+
Invoicing::CurrencyValue::CURRENCIES['ZWD'] = {:symbol => 'ZW$', :round => 5_000_000}
|
9
|
+
|
10
|
+
class CurrencyValueTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
class CurrencyValueRecord < ActiveRecord::Base
|
13
|
+
validates_numericality_of :amount
|
14
|
+
acts_as_currency_value :amount, :tax_amount, :currency => 'currency_code'
|
15
|
+
end
|
16
|
+
|
17
|
+
# In Finland and the Netherlands, Euro amounts are commonly rounded to the nearest 5 cents.
|
18
|
+
class EurosInFinlandRecord < ActiveRecord::Base
|
19
|
+
set_table_name 'no_currency_column_records'
|
20
|
+
acts_as_currency_value :amount, :currency_code => 'EUR', :round => 0.05, :space => true
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_format_small_number
|
24
|
+
assert_equal "€0.02", CurrencyValueRecord.find(2).tax_amount_formatted
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_format_thousands_separators
|
28
|
+
assert_equal "€98,765,432.00", CurrencyValueRecord.find(2).amount_formatted
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_format_no_decimal_point
|
32
|
+
assert_equal "¥8,888", CurrencyValueRecord.find(4).amount_formatted
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_format_suffix_unit
|
36
|
+
assert_equal "5,432.00 元", CurrencyValueRecord.find(3).amount_formatted
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_format_unknown_currency
|
40
|
+
assert_equal "123.00 XXX", CurrencyValueRecord.find(5).amount_formatted
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_format_with_custom_currency
|
44
|
+
record = CurrencyValueRecord.new(:currency_code => 'ZWD', :amount => BigDecimal('50000000000'))
|
45
|
+
assert_equal "ZW$50,000,000,000", record.amount_formatted # price of 1 egg on 18 July 2008
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_format_without_currency_column
|
49
|
+
assert_equal "€ 95.15", EurosInFinlandRecord.find(1).amount_formatted
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_format_custom_value
|
53
|
+
assert_equal "€1,357.90", CurrencyValueRecord.find(2).format_currency_value(BigDecimal('1357.9'))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_load_from_database_and_format
|
57
|
+
assert_equal BigDecimal('123.45'), CurrencyValueRecord.find(1).amount
|
58
|
+
assert_equal "£123.45", CurrencyValueRecord.find(1).amount_formatted
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_new_record_from_string_and_format
|
62
|
+
record = CurrencyValueRecord.new(:amount => '44.44', :currency_code => 'USD')
|
63
|
+
assert_equal BigDecimal('44.44'), record.amount
|
64
|
+
assert_equal "$44.44", record.amount_formatted
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_new_record_from_big_decimal_and_format
|
68
|
+
record = CurrencyValueRecord.new(:amount => BigDecimal('3.33'), :currency_code => 'USD')
|
69
|
+
assert_equal BigDecimal('3.33'), record.amount
|
70
|
+
assert_equal "$3.33", record.amount_formatted
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_assign_float_to_new_record_and_format
|
74
|
+
record = CurrencyValueRecord.new
|
75
|
+
record.amount = 44.44
|
76
|
+
record.currency_code = 'USD'
|
77
|
+
assert_equal BigDecimal('44.44'), record.amount
|
78
|
+
assert_equal "$44.44", record.amount_formatted
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_assign_to_new_record_omitting_currency
|
82
|
+
record = CurrencyValueRecord.new
|
83
|
+
record.amount = 44.44
|
84
|
+
assert_equal BigDecimal('44.44'), record.amount
|
85
|
+
assert_equal "44.44", record.amount_formatted
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_assign_nothing_to_new_record_with_numericality_validation
|
89
|
+
record = CurrencyValueRecord.new(:currency_code => 'USD')
|
90
|
+
assert_nil record.amount
|
91
|
+
assert_equal '', record.amount_formatted
|
92
|
+
assert !record.valid?
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_assign_nothing_to_new_record_without_numericality_validation
|
96
|
+
record = CurrencyValueRecord.new(:amount => 1, :currency_code => 'USD')
|
97
|
+
assert_nil record.tax_amount
|
98
|
+
assert_equal '', record.tax_amount_formatted
|
99
|
+
assert record.valid?
|
100
|
+
record.save!
|
101
|
+
assert_equal([{'amount' => '1.0000', 'tax_amount' => nil}],
|
102
|
+
ActiveRecord::Base.connection.select_all("SELECT amount, tax_amount FROM currency_value_records WHERE id=#{record.id}"))
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_assign_invalid_value_to_new_record_with_numericality_validation
|
106
|
+
record = CurrencyValueRecord.new(:amount => 'plonk', :currency_code => 'USD')
|
107
|
+
assert_equal BigDecimal('0.00'), record.amount
|
108
|
+
assert_equal 'plonk', record.amount_before_type_cast
|
109
|
+
assert_equal '', record.amount_formatted
|
110
|
+
assert !record.valid?
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_assign_invalid_value_to_new_record_without_numericality_validation
|
114
|
+
record = CurrencyValueRecord.new(:amount => 1, :tax_amount => 'plonk', :currency_code => 'USD')
|
115
|
+
assert_equal BigDecimal('0.00'), record.tax_amount
|
116
|
+
assert_equal 'plonk', record.tax_amount_before_type_cast
|
117
|
+
assert_equal '', record.tax_amount_formatted
|
118
|
+
assert record.valid?
|
119
|
+
record.save!
|
120
|
+
assert_equal([{'amount' => '1.0000', 'tax_amount' => '0.0000'}],
|
121
|
+
ActiveRecord::Base.connection.select_all("SELECT amount, tax_amount FROM currency_value_records WHERE id=#{record.id}"))
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_overwrite_existing_record_with_valid_value
|
125
|
+
record = CurrencyValueRecord.find(4)
|
126
|
+
record.amount = '12.34'
|
127
|
+
record.currency_code = 'EUR'
|
128
|
+
assert_equal BigDecimal('12.34'), record.amount
|
129
|
+
assert_equal '12.34', record.amount_before_type_cast
|
130
|
+
assert_equal "€12.34", record.amount_formatted
|
131
|
+
record.save!
|
132
|
+
assert_equal([{'amount' => '12.3400', 'currency_code' => 'EUR'}],
|
133
|
+
ActiveRecord::Base.connection.select_all("SELECT amount, currency_code FROM currency_value_records WHERE id=#{record.id}"))
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_overwrite_existing_record_with_nil
|
137
|
+
record = CurrencyValueRecord.find(4)
|
138
|
+
record.tax_amount = nil
|
139
|
+
assert_nil record.tax_amount
|
140
|
+
assert_nil record.tax_amount_before_type_cast
|
141
|
+
assert_equal '', record.tax_amount_formatted
|
142
|
+
record.save!
|
143
|
+
assert_equal([{'amount' => '8888.0000', 'tax_amount' => nil, 'currency_code' => 'JPY'}],
|
144
|
+
ActiveRecord::Base.connection.select_all("SELECT amount, tax_amount, currency_code FROM currency_value_records WHERE id=#{record.id}"))
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_rounding_on_new_record_with_currency_column
|
148
|
+
record = CurrencyValueRecord.new(:amount => '1234.5678', :currency_code => 'JPY')
|
149
|
+
assert_equal BigDecimal('1235'), record.amount
|
150
|
+
assert_equal '1234.5678', record.amount_before_type_cast
|
151
|
+
record.save!
|
152
|
+
assert_equal([{'amount' => '1235.0000'}],
|
153
|
+
ActiveRecord::Base.connection.select_all("SELECT amount FROM currency_value_records WHERE id=#{record.id}"))
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_rounding_on_overwriting_record_with_currency_column
|
157
|
+
record = CurrencyValueRecord.find(1)
|
158
|
+
record.amount = 10.0/3.0
|
159
|
+
assert_equal BigDecimal('3.33'), record.amount
|
160
|
+
assert_equal 10.0/3.0, record.amount_before_type_cast
|
161
|
+
record.save!
|
162
|
+
assert_equal([{'amount' => '3.3300'}],
|
163
|
+
ActiveRecord::Base.connection.select_all("SELECT amount FROM currency_value_records WHERE id=1"))
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_rounding_on_new_record_with_default_currency
|
167
|
+
record = EurosInFinlandRecord.new(:amount => '1234.5678')
|
168
|
+
assert_equal BigDecimal('1234.55'), record.amount
|
169
|
+
assert_equal '1234.5678', record.amount_before_type_cast
|
170
|
+
record.save!
|
171
|
+
assert_equal([{'amount' => '1234.5500'}],
|
172
|
+
ActiveRecord::Base.connection.select_all("SELECT amount FROM no_currency_column_records WHERE id=#{record.id}"))
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_rounding_on_overwriting_record_with_default_currency
|
176
|
+
record = EurosInFinlandRecord.find(1)
|
177
|
+
record.amount = '98.7654321'
|
178
|
+
assert_equal BigDecimal('98.75'), record.amount
|
179
|
+
assert_equal '98.7654321', record.amount_before_type_cast
|
180
|
+
record.save!
|
181
|
+
assert_equal([{'amount' => '98.7500'}],
|
182
|
+
ActiveRecord::Base.connection.select_all("SELECT amount FROM no_currency_column_records WHERE id=1"))
|
183
|
+
end
|
184
|
+
end
|