File Coverage

blib/lib/Business/TNTPost/NL.pm
Criterion Covered Total %
statement 64 64 100.0
branch 43 50 86.0
condition 8 12 66.6
subroutine 12 12 100.0
pod 3 3 100.0
total 130 141 92.2


line stmt bran cond sub pod time code
1             package Business::TNTPost::NL;
2              
3 8     8   380722 use strict;
  8         25  
  8         15887  
4 8     8   6179 use Business::TNTPost::NL::Data qw/:ALL/;
  8         24  
  8         1436  
5 8     8   92 use Carp;
  8         98  
  8         618  
6 8     8   48 use List::Util qw/reduce/;
  8         13  
  8         1266  
7              
8             our $VERSION = 0.11;
9             our $ERROR = undef;
10              
11 8     8   50 use base qw/Class::Accessor::Fast/;
  8         16  
  8         21720  
12              
13             BEGIN {
14 8     8   54203 __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::TNTPost::NL - Calculate Dutch (TNT Post) shipping costs
24              
25             =head1 SYNOPSIS
26              
27             use Business::TNTPost::NL;
28              
29             my $tnt = Business::TNTPost::NL->new();
30             $tnt->country('DE');
31             $tnt->weight('534');
32             $tnt->large(1);
33             $tnt->priority(1);
34             $tnt->tracktrace(1);
35             $tnt->register(1);
36              
37             my $costs = $tnt->calculate or die $Business::TNTPost::NL::ERROR;
38              
39              
40             or
41              
42             use Business::TNTPost::NL;
43              
44             my $tnt = Business::TNTPost::NL->new();
45             my $costs = $tnt->calculate(
46             country =>'DE',
47             weight => 534,
48             large => 1,
49             tracktrace => 1,
50             register => 1,
51             ) or die $Business::TNTPost::NL::ERROR;
52              
53             =head1 DESCRIPTION
54              
55             This module calculates the shipping costs for the Dutch TNT Post,
56             based on country, weight and priority shipping (or not), etc.
57              
58             The shipping cost information is based on 'Tarieven Januari 2011'.
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::TNTPost::NL::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 12186 my ( $class, %parameters ) = @_;
76 19   33     260 my $self = bless( {}, ref($class) || $class );
77 19         75 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 TNT Post (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 66 my ( $self, $cc ) = @_;
97              
98 19 50       103 if ($cc) {
99 19         113 my $zones = Business::TNTPost::NL::Data::zones();
100 19 100       133 $self->zone( defined $zones->{$cc} ? $zones->{$cc} : '4' );
101             }
102              
103 19         449 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::TNTPost::NL::ERROR
118             in that case).
119              
120             =cut
121              
122             sub calculate {
123 19     19 1 1039 my ( $self, %opt ) = @_;
124              
125             # Set the options
126 19         67 for (
127             qw/country weight large priority tracktrace
128             register receipt machine/
129             )
130             {
131 152 100       927 $self->$_( $opt{$_} ) if ( defined $opt{$_} );
132             }
133              
134 19 50 33     262 croak "Not enough information!"
135             unless ( defined $self->zone && defined $self->weight );
136              
137             # > 2000 grams automatically means 'tracktrace'
138 19 100       492 $self->tracktrace(1) if ( $self->weight > 2000 );
139              
140             # Zone 1..4 (with tracktrace) automagically means 'priority'
141 19 100       201 $self->priority(1) if ( $self->tracktrace );
142              
143             # Zone 3,4 + small automagically means 'priority'
144 19 100 100     149 $self->priority(1) if( $self->zone > 3 && !$self->large );
145              
146             # All zones (above NL) are now priority by default
147 19 100       210 $self->priority(1) if $self->zone;
148              
149             # Fetch the interesting table
150 19         228 my $ref = _pointer_to_element( table(), $self->_generate_path );
151 19         726 my $table = $$ref;
152              
153 19         39 my $highest = 0;
154 19         38 foreach my $key ( keys %{$table} ) {
  19         318  
155 45         447 my ( $lo, $hi ) = split ',', $key;
156 45 100       165 $highest = $hi if ( $hi > $highest );
157 45 100 100     152 if ( $self->weight >= $lo && $self->weight <= $hi ) {
158 18         328 $self->cost( $table->{$key} );
159 18         156 last;
160             }
161             }
162 19 100       92 $ERROR = $self->weight - $highest . " grams too heavy (max: $highest gr.)"
163             if ( $highest < $self->weight );
164              
165 19 100       263 return ( $self->cost ) ? sprintf( "%0.2f", $self->cost ) : undef;
166             }
167              
168             =pod
169              
170             =head3 _generate_path
171              
172             Internal method to create the path to walk through the pricing table.
173             Don't call this, use L instead.
174              
175             =cut
176              
177             sub _generate_path {
178 19     19   115 my $self = shift;
179              
180 19         43 my @p;
181              
182 19 100       113 if ( $self->zone ) {
183 15         142 push @p, 'world'; # world
184 15 100       67 if ( $self->register ) {
    100          
185 7 100       61 push @p, 'register', # w/register
    100          
186             ( $self->zone < 4 ) # w/register/(europe|world)
187             ? 'europe'
188             : 'world',
189             ( $self->machine ) # w/register/(e|w)/(stamp|machine)
190             ? 'machine'
191             : 'stamp';
192             }
193             elsif ( $self->tracktrace ) {
194 1         14 push @p, 'plus', 'zone', $self->zone; # w/plus/zone/[1..4]
195             }
196             else {
197 7 100       154 push @p, 'basic', # w/basic
    50          
198             ( $self->zone < 4 ) # w/basic/(europe|world)
199             ? 'europe'
200             : 'world',
201             ( $self->large ) # w/basic/(e|w)/(large|small)
202             ? 'large'
203             : 'small';
204              
205 7 50       123 if( !$self->large ) {
206             # w/basic/(e|w)/small/(machine|stamp)
207 7 50       67 push @p, ( $self->machine ) ? 'machine' : 'stamp';
208             }
209              
210             ### priority is now always the default
211 7         57 push @p, 'priority';
212             #push @p,
213             # ( $self->priority ) # w/basic/(e|w)/(l|s)/(m|s)?/(p|s)
214             # ? 'priority'
215             # : 'standard';
216             }
217             }
218             else {
219 4         43 push @p, 'netherlands'; # netherlands
220 4 100       21 if ( $self->register ) {
221 2         14 push @p, 'register'; # n/register
222             }
223             else {
224 2 50       21 push @p, ( $self->large ) # n/(large|small)
225             ? 'large'
226             : 'small';
227             }
228 4 50       30 ( push @p, ( $self->machine ) ? 'machine' : 'stamp' )
    100          
229             unless $self->large;;
230             }
231             # debug
232             # print (join " :: ", @p), "\n";
233 19         368 return @p;
234             }
235              
236             =pod
237              
238             =head3 _pointer_to_element
239              
240             Blame L for this internal method. It's using L
241             to "grep" the information needed.
242              
243             Don't call this, use L instead.
244              
245             =cut
246              
247             sub _pointer_to_element { # Thanks 'merlyn'!
248 19     19   333 require List::Util;
249 19     84   356 return List::Util::reduce( sub { \( $$a->{$b} ) }, \shift, @_ );
  84         278  
250             }
251              
252             =pod
253              
254             =head3 weight
255              
256             Sets and/or returns the weight of the parcel in question in grams.
257              
258             This value is mandatory for the calculations.
259              
260             =head3 large
261              
262             Sets and/or returns the value of this option. Defaults to undef (meaning:
263             the package will fit through the mail slot).
264              
265             =head3 tracktrace
266              
267             Sets and/or returns the value of this options. Defaults to undef (meaning:
268             no track & trace feature wanted). When a parcel destined for abroad
269             weighs over 2 kilograms, default is 1, while over 2kg it's not even
270             optional anymore.
271              
272             =head3 register
273              
274             Sets and/or returns the value of this options. Defaults to undef (meaning:
275             parcel is not registered (Dutch: aangetekend)).
276              
277             =head3 machine
278              
279             Sets and/or returns the value of this options. Defaults to undef (meaning:
280             stamps will be used, not the machine (Dutch: frankeermachine)).
281              
282             Only interesting for destinies within NL. Note that "Pakketzegel AVP"
283             and "Easystamp" should also use this option.
284              
285             =head1 BUGS
286              
287             Please do report bugs/patches to
288             L
289              
290             =head1 CAVEAT
291              
292             The Dutch postal agency (TNT Post) uses many, many, many various ways
293             for you to ship your parcels. Some of them are included in this module,
294             but a lot of them not (maybe in the future? Feel free to patch ;-)
295              
296             This module handles the following shipping ways (page numbers refer to the
297             TNT Post booklet (sorry, all in Dutch)).
298              
299             'Brieven' and 'Pakketten' either paid by 'frankeermachine'. For both,
300             'aangetekend' is optional. The pagenumbers used: 18, 19, 20, 21.
301              
302             These should be the most commom methods of shipment.
303              
304             =head1 AUTHOR
305              
306             Menno Blom,
307             Eblom@cpan.orgE,
308             L
309              
310             =head1 COPYRIGHT
311              
312             This program is free software; you can redistribute
313             it and/or modify it under the same terms as Perl itself.
314              
315             The full text of the license can be found in the
316             LICENSE file included with this module.
317              
318             =head1 SEE ALSO
319              
320             L,
321             L,
322             L
323              
324             =cut
325              
326             1;