File Coverage

blib/lib/Interchange6/Currency.pm
Criterion Covered Total %
statement 91 92 98.9
branch 23 24 100.0
condition n/a
subroutine 27 28 96.4
pod 12 12 100.0
total 153 156 98.7


line stmt bran cond sub pod time code
1             package Interchange6::Currency;
2              
3             =head1 NAME
4              
5             Interchange6::Currency - Currency objects for Interchange 6
6              
7             =head1 VERSION
8              
9             0.100
10              
11             =cut
12              
13             our $VERSION = '0.100';
14              
15 2     2   1342341 use Moo;
  2         3547108  
  2         17  
16             extends 'CLDR::Number::Format::Currency';
17              
18 2     2   36400 use Carp;
  2         7  
  2         244  
19 2     2   2254 use Class::Load qw/load_class/;
  2         360275  
  2         125  
20 2     2   3158 use Math::BigFloat;
  2         45149  
  2         11  
21 2     2   166588 use Safe::Isa;
  2         874  
  2         235  
22 2     2   1509 use Sub::Quote qw/quote_sub/;
  2         7737  
  2         111  
23 2     2   1614 use namespace::clean;
  2         13013  
  2         10  
24             use overload
25 0     0   0 '0+' => sub { shift->value },
26 52     52   34238 '""' => sub { shift->as_string },
27 2         37 '+' => \&_add,
28             '-' => \&_subtract,
29             '*' => \&_multiply,
30             '/' => \&_divide,
31             '%' => \&modulo,
32             '<=>' => \&cmp_value,
33             'cmp' => \&cmp,
34             '=' => \&clone,
35 2     2   807 ;
  2         4  
36              
37             =head1 DESCRIPTION
38              
39             Extends L with accurate calculation functions
40             using L.
41              
42             Many useful standard operators are overloaded and return currency objects
43             if appropriate.
44              
45             =head1 ATTRIBUTES
46              
47             =head2 value
48              
49             Value as simple decimal, e.g.: 3.45
50              
51             All values are coerced into L.
52              
53             =cut
54              
55             has value => (
56             is => 'rwp',
57             required => 1,
58             coerce => quote_sub(q{ $_[0]->$_isa("Math::BigFloat") ? $_[0] : Math::BigFloat->new( $_[0] ) }),
59             );
60              
61             # check for currency objects with different currency codes and if arg
62             # is a currency object return its value
63             sub _clean_arg {
64 78     78   774 my ( $self, $arg ) = @_;
65              
66             # uncoverable branch true
67 78 50       249 croak "_clean_arg is a class method" unless $self->$_isa(__PACKAGE__);
68              
69 78 100       1110 if ( $arg->$_isa(__PACKAGE__) ) {
70 19 100       727 croak "Cannot perform calculation when currencies do not match"
71             if $self->currency_code ne $arg->currency_code;
72 12         175 return $arg->value;
73             }
74 59         668 return $arg;
75             }
76              
77             =head2 converter_class
78              
79             Defaults to L.
80              
81             The class name which handles conversion to a new C.
82              
83             The converter class can be any class that supports the following method
84             signature:
85              
86             sub convert {
87             my ($self, $price, $from, $to) = @_;
88            
89             return $converted_price;
90             };
91              
92             =cut
93              
94             has converter_class => (
95             is => 'ro',
96             isa => quote_sub(q{ die "$_[0] is not a valid class name" unless ( ref(\$_[0]) eq 'SCALAR' && $_[0] =~ /^\S+$/ ) }),
97             default => "Finance::Currency::Convert::WebserviceX",
98             );
99              
100             =head2 converter
101              
102             Vivified L.
103              
104             =cut
105              
106             has converter => (
107             is => 'lazy',
108             isa => quote_sub(q{ die "Not a valid converter class" unless $_[0]->$_can('convert') }),
109             init_arg => undef,
110             );
111              
112             sub _build_converter {
113 4     4   843 my $self = shift;
114 4         26 load_class( $self->converter_class );
115 3         1398 return $self->converter_class->new;
116             }
117              
118             =head1 METHODS
119              
120             =head2 BUILD
121              
122             Sets precision for automatic rounding of L to
123             L.
124              
125             =cut
126              
127             sub BUILD {
128 43     43 1 3486279 my $self = shift;
129              
130             # force cash trigger so we can reset precision on value
131 43         878 $self->cash( $self->cash );
132 43         21212 $self->value->precision( -$self->maximum_fraction_digits );
133             }
134              
135             =head2 clone %new_attrs?
136              
137             Returns clone of the currency object possibly with new attribute values (if
138             any are supplied).
139              
140             =cut
141              
142             sub clone {
143 34     34 1 4044 my ( $self, %new_attrs ) = @_;
144 34         1065 return __PACKAGE__->new(
145             value => $self->value,
146             currency_code => $self->currency_code,
147             locale => $self->locale,
148             %new_attrs,
149             );
150             }
151              
152             =head2 convert $new_corrency_code
153              
154             Convert to new currency using L.
155              
156             B If C is called in void context then the currency object
157             is mutated in place. If called in list or scalar context then the original
158             object is not modified and a new L object is returned
159             instead.
160              
161             =cut
162              
163             sub convert {
164 11     11 1 13600 my ( $self, $new_code ) = @_;
165              
166 11 100       318 if ( $self->currency_code eq $new_code ) {
167              
168             # currency code has not changed
169 2 100       18 if ( defined wantarray ) {
170              
171             # called in list or scalar context
172 1         4 return $self->clone;
173             }
174             else {
175              
176             # void context
177 1         5 return;
178             }
179             }
180             else {
181              
182             # remove precision before conversion since new currency may have
183             # different maximum_fraction_digits and we don't want to lose accuracy
184 9         108 $self->value->precision(undef);
185              
186             # currency code has changed so convert via converter_class
187 9         337 my $new_value =
188             $self->converter->convert( $self->value, $self->currency_code,
189             $new_code );
190              
191 8 100       3030 croak "convert failed" unless defined $new_value;
192              
193 7 100       20 if ( defined wantarray ) {
194              
195             # called in list or scalar context
196              
197 5         20 return $self->clone(
198             currency_code => $new_code,
199             value => $new_value,
200             );
201             }
202             else {
203              
204             # void context
205              
206 2         55 $self->currency_code($new_code);
207 2         1371 $self->_set_value($new_value);
208              
209             # force cash trigger so we can reset precision on value
210 2         830 $self->cash( $self->cash );
211 2         1060 $self->value->precision( -$self->maximum_fraction_digits );
212              
213 2         441 return;
214             }
215             }
216             }
217              
218             =head2 as_string
219              
220             Stringified formatted currency, e.g.: $3.45
221              
222             =cut
223              
224             sub as_string {
225 74     74 1 32965 return $_[0]->format( $_[0]->value );
226             }
227              
228             =head2 stringify
229              
230             Alias for L.
231              
232             =cut
233              
234             sub stringify {
235 1     1 1 2429 return $_[0]->as_string;
236             }
237              
238             =head2 add $arg
239              
240             Add C<$arg> to L in place.
241              
242             =cut
243              
244             sub add {
245 3     3 1 8022 my ( $self, $arg ) = @_;
246 3         17 $self->value->badd( $self->_clean_arg($arg) );
247             }
248              
249             # for overloaded '+'
250             sub _add {
251 8     8   14003 my ( $self, $arg ) = @_;
252 8         41 $self->clone(
253             value => $self->value->copy->badd( $self->_clean_arg($arg) ) );
254             }
255              
256             =head2 subtract $arg
257              
258             Subtract C<$arg> from L in place.
259              
260             =cut
261              
262             sub subtract {
263 3     3 1 4302 my ( $self, $arg ) = @_;
264 3         17 $self->value->bsub( $self->_clean_arg($arg) );
265             }
266              
267             # for overloaded '-'
268             sub _subtract {
269 8     8   10063 my ( $self, $arg, $swap ) = @_;
270 8         38 my $result = $self->value->copy->bsub( $self->_clean_arg($arg) );
271 8 100       3141 $self->clone( value => $swap ? $result->bneg : $result );
272             }
273              
274             =head2 multiply $arg
275              
276             Multiply L by C<$arg> in place.
277              
278             =cut
279              
280             sub multiply {
281 3     3 1 4129 my ( $self, $arg ) = @_;
282 3         15 $self->value->bmul( $self->_clean_arg($arg) );
283             }
284              
285             # for overloaded '*'
286             sub _multiply {
287 2     2   3017 my ( $self, $arg ) = @_;
288 2         16 $self->clone(
289             value => $self->value->copy->bmul( $self->_clean_arg($arg) ) );
290             }
291              
292             =head2 divide $arg
293              
294             Divide L by C<$arg> in place.
295              
296             =cut
297              
298             sub divide {
299 3     3 1 4174 my ( $self, $arg ) = @_;
300 3         13 $self->value->bdiv( $self->_clean_arg($arg) );
301             }
302              
303             # for overloaded '/'
304             sub _divide {
305 5     5   7998 my ( $self, $arg, $swap ) = @_;
306 5         9 my $result;
307 5 100       16 if ($swap) {
308 1         5 $result =
309             Math::BigFloat->new( $self->_clean_arg($arg) )->bdiv( $self->value );
310             }
311             else {
312 4         25 $result = $self->value->copy->bdiv( $self->_clean_arg($arg) );
313             }
314 5         3225 $self->clone( value => $result );
315             }
316              
317             =head2 modulo $arg
318              
319             Return L C<%> C<$arg> as currency object.
320              
321             =cut
322              
323             sub modulo {
324 6     6 1 8261 my ( $self, $arg, $swap ) = @_;
325 6         10 my $result;
326 6 100       19 if ($swap) {
327 1         5 $result =
328             Math::BigFloat->new( $self->_clean_arg($arg) )->bmod( $self->value );
329             }
330             else {
331 5         25 $result = $self->value->copy->bmod( $self->_clean_arg($arg) );
332             }
333 5         2384 $self->clone( value => $result );
334             }
335              
336             =head2 cmp_value $arg
337              
338             Equivalent to L C<< <=> >> C<$arg>.
339              
340             =cut
341              
342             sub cmp_value {
343 37     37 1 30509 my ( $self, $arg, $swap ) = @_;
344 37 100       99 if ($swap) {
345 3         10 return Math::BigFloat->new( $self->_clean_arg($arg) )
346             ->bcmp( $self->value );
347             }
348             else {
349 34         128 return $self->value->bcmp( $self->_clean_arg($arg) );
350             }
351             }
352              
353             =head2 cmp $arg
354              
355             String comparison.
356              
357             Not always useful in itself since string comparison of stringified currency
358             objects might not produce what you expect depending on locale and currency
359             code.
360              
361             =cut
362              
363             sub cmp {
364 29     29 1 33515 my ( $self, $arg, $swap ) = @_;
365 29 100       88 if ($swap) {
366 3         13 return "$arg" cmp "$self";
367             }
368             else {
369 26         74 return "$self" cmp "$arg";
370             }
371             }
372              
373             1;