File Coverage

blib/lib/Business/PostNL.pm
Criterion Covered Total %
statement 62 62 100.0
branch 34 40 85.0
condition 5 9 55.5
subroutine 12 12 100.0
pod 3 3 100.0
total 116 126 92.0


line stmt bran cond sub pod time code
1             package Business::PostNL;
2              
3 7     7   345924 use strict;
  7         19  
  7         316  
4 7     7   5976 use Business::PostNL::Data qw/:ALL/;
  7         22  
  7         1270  
5 7     7   76 use Carp;
  7         82  
  7         593  
6 7     7   45 use List::Util qw/reduce/;
  7         13  
  7         2259  
7              
8             our $VERSION = 0.14;
9             our $ERROR = undef;
10              
11 7     7   46 use base qw/Class::Accessor::Fast/;
  7         12  
  7         10002  
12              
13             BEGIN {
14 7     7   33923 __PACKAGE__->mk_accessors(
15             qw/cost large machine priority receipt register tracktrace weight zone/
16             );
17             }
18              
19             =pod
20              
21             =head1 NAME
22              
23             Business::PostNL - Calculate Dutch (PostNL) shipping costs
24              
25             =head1 SYNOPSIS
26              
27             use Business::PostNL;
28              
29             my $tnt = Business::PostNL->new();
30             $tnt->country('DE');
31             $tnt->weight('534');
32             $tnt->large(1);
33             $tnt->tracktrace(1);
34             $tnt->register(1);
35              
36             my $costs = $tnt->calculate or die $Business::PostNL::ERROR;
37              
38              
39             or
40              
41             use Business::PostNL;
42              
43             my $tnt = Business::PostNL->new();
44             my $costs = $tnt->calculate(
45             country =>'DE',
46             weight => 534,
47             large => 1,
48             tracktrace => 1,
49             register => 1,
50             ) or die $Business::PostNL::ERROR;
51              
52             =head1 DESCRIPTION
53              
54             This module calculates the shipping costs for the Dutch PostNL,
55             based on country, and weight etc.
56              
57             The shipping cost information is based on 'Posttarieven
58             Per januari 2014'.
59              
60             It returns the shipping costs in euro or undef (which usually means
61             the parcel is heavier than the maximum allowed weight; check
62             C<$Business::PostNL::ERROR>).
63              
64             =head2 METHODS
65              
66             The following methods can be used
67              
68             =head3 new
69              
70             C creates a new C object. No more, no less.
71              
72             =cut
73              
74             sub new {
75 19     19 1 11535 my ( $class, %parameters ) = @_;
76 19   33     203 my $self = bless( {}, ref($class) || $class );
77 19         74 return $self;
78             }
79              
80             =pod
81              
82             =head3 country
83              
84             Sets the country (ISO 3166, 2-letter country code) and returns the
85             zone number used by PostNL (or 0 for The Netherlands (NL)).
86              
87             This value is mandatory for the calculations.
88              
89             Note that the reserved IC has been used for the Canary Islands. This
90             has not been adopted by ISO 3166 yet. The Channel Islands are completely
91             ignored due to a lack of a code.
92              
93             =cut
94              
95             sub country {
96 19     19 1 46 my ( $self, $cc ) = @_;
97              
98 19 50       122 if ($cc) {
99 19         124 my $zones = Business::PostNL::Data::zones();
100 19 100       138 $self->zone( defined $zones->{$cc} ? $zones->{$cc} : '4' );
101             }
102              
103 19         355 return $self->zone;
104             }
105              
106             =pod
107              
108             =head3 calculate
109              
110             Method to calculate the actual shipping cost based on the input (see
111             methods above). These options can also be passed straight in to this method
112             (see L).
113              
114             Two settings are mandatory: country and weight. The rest are given a
115             default value that will be used unless told otherwise.
116              
117             Returns the shipping costs in euro, or undef (see $Business::PostNL::ERROR
118             in that case).
119              
120             =cut
121              
122             sub calculate {
123 19     19 1 743 my ( $self, %opt ) = @_;
124              
125             # Set the options
126 19         61 for ( qw/country weight large tracktrace register machine/) {
127 114 100       659 $self->$_( $opt{$_} ) if ( defined $opt{$_} );
128             }
129              
130 19 50 33     91 croak "Not enough information!"
131             unless ( defined $self->zone && defined $self->weight );
132              
133             # > 2000 grams automatically means 'tracktrace'
134 19 100       428 $self->tracktrace(1) if ( $self->weight > 2000 );
135              
136             # Fetch the interesting table
137 19         246 my $ref = _pointer_to_element( table(), $self->_generate_path );
138 19         1061 my $table = $$ref;
139              
140 19         43 my $highest = 0;
141 19         36 foreach my $key ( keys %{$table} ) {
  19         130  
142 34         391 my ( $lo, $hi ) = split ',', $key;
143 34 100       138 $highest = $hi if ( $hi > $highest );
144 34 100 100     116 if ( $self->weight >= $lo && $self->weight <= $hi ) {
145 17         343 $self->cost( $table->{$key} );
146 17         162 last;
147             }
148             }
149 19 100       99 $ERROR = $self->weight - $highest . " grams too heavy (max: $highest gr.)"
150             if ( $highest < $self->weight );
151              
152 19 100       169 return ( $self->cost ) ? sprintf( "%0.2f", $self->cost ) : undef;
153             }
154              
155             =pod
156              
157             =head3 _generate_path
158              
159             Internal method to create the path to walk through the pricing table.
160             Don't call this, use L instead.
161              
162             =cut
163              
164             sub _generate_path {
165 19     19   49 my $self = shift;
166              
167 19         44 my @p;
168              
169 19 100       112 if ( $self->zone ) {
170 13         125 push @p, 'world'; # world
171              
172 13 100       103 if( $self->large ) {
173 1         9 push @p, 'large', # w/large
174             'zone', # w/large/zone
175             $self->zone; # w/large/zone/[1..4]
176 1 50       8 push @p,
177             ( $self->machine )
178             ? 'machine' # w/large/zone/[1..4]/machine
179             : 'stamp'; # w/large/zone/[1..4]/stamp
180              
181 1 50       10 push @p,
    50          
182             ( $self->register )
183             ? 'register' # w/large/zone/[1..4]/(m|s)/register
184             : ( $self->tracktrace )
185             ? 'tracktrace' # w/large/zone/[1..4]/(m|s)/tracktrace
186             : 'normal'; # w/large/zone/[1..4]/(m|s)/normal
187              
188             }
189             else {
190 12         90 push @p, 'small'; # w/small
191 12 100       49 push @p,
192             ( $self->zone < 4 )
193             ? 'europe' # w/small/europe
194             : 'world'; # w/small/world
195 12 100       1249 push @p,
196             ( $self->machine )
197             ? 'machine' # w/small/(e|w)/machine
198             : 'stamp'; # w/small/(e|w)/stamp
199 12 100       108 push @p,
200             ( $self->register )
201             ? 'register' # w/small/(e|w)/(m|s)/register
202             : 'normal'; # w/small/(e|w)/(m|s)/normal
203             }
204             }
205             else {
206 6         58 push @p, 'netherlands'; # netherlands
207 6 100       35 if ( $self->register ) {
208 2         18 push @p, 'register'; # n/register
209             }
210             else {
211 4 50       36 push @p, ( $self->large ) # n/(large|small)
212             ? 'large'
213             : 'small';
214             }
215 6 100       47 push @p,
216             ( $self->machine )
217             ? 'machine' # n/(r|l|s)/machine
218             : 'stamp'; # n/(r|l|s)/stamp
219             }
220             #print (join " :: ", @p), "\n";
221 19         281 return @p;
222             }
223              
224             =pod
225              
226             =head3 _pointer_to_element
227              
228             Blame L for this internal method. It's using L
229             to "grep" the information needed.
230              
231             Don't call this, use L instead.
232              
233             =cut
234              
235             sub _pointer_to_element { # Thanks 'merlyn'!
236 19     19   276 require List::Util;
237 19     84   349 return List::Util::reduce( sub { \( $$a->{$b} ) }, \shift, @_ );
  84         194  
238             }
239              
240             =pod
241              
242             =head3 weight
243              
244             Sets and/or returns the weight of the parcel in question in grams.
245              
246             This value is mandatory for the calculations.
247              
248             =head3 large
249              
250             Sets and/or returns the value of this option. Defaults to undef (meaning:
251             the package will fit through the mail slot).
252              
253             =head3 priority [DEPRECATED]
254              
255             PostNL still requires you to put a priority sticker on your letters and
256             parcels, but this seems to be solely for speed of delivery. I couldn't
257             find any price difference, hence this setting is ignored from now on and
258             only here for backwards compatability.
259              
260             =head3 tracktrace
261              
262             Sets and/or returns the value of this options. Defaults to undef (meaning:
263             no track & trace feature wanted). When a parcel destined for abroad
264             weighs over 2 kilograms, default is 1, while over 2kg it's not even
265             optional anymore.
266              
267             =head3 register
268              
269             Sets and/or returns the value of this options. Defaults to undef (meaning:
270             parcel is not registered (Dutch: aangetekend)).
271              
272             =head3 receipt [DEPRECATED]
273              
274             No longer an option, solely here for backwards compatibility.
275              
276             =head3 machine
277              
278             Sets and/or returns the value of this options. Defaults to undef (meaning:
279             stamps will be used, not the machine (Dutch: frankeermachine)).
280              
281             Only interesting for destinies within NL. Note that "Pakketzegel AVP"
282             and "Easystamp" should also use this option.
283              
284             =head1 BUGS
285              
286             Please do report bugs/patches to
287             L
288              
289             =head1 CAVEAT
290              
291             The Dutch postal agency (PostNL) uses many, many, many various ways
292             for you to ship your parcels. Some of them are included in this module,
293             but a lot of them not (maybe in the future? Feel free to patch ;-)
294              
295             =head1 AUTHOR
296              
297             Menno Blom,
298             Eblom@cpan.orgE
299              
300             =head1 COPYRIGHT
301              
302             This program is free software; you can redistribute
303             it and/or modify it under the same terms as Perl itself.
304              
305             The full text of the license can be found in the
306             LICENSE file included with this module.
307              
308             =head1 SEE ALSO
309              
310             L,
311             L,
312             L
313              
314             =cut
315              
316             1;