File Coverage

blib/lib/Business/DK/PO.pm
Criterion Covered Total %
statement 91 91 100.0
branch 26 26 100.0
condition n/a
subroutine 19 19 100.0
pod 3 3 100.0
total 139 139 100.0


line stmt bran cond sub pod time code
1             package Business::DK::PO;
2              
3 7     7   220679 use strict;
  7         16  
  7         343  
4 7     7   42 use warnings;
  7         14  
  7         890  
5 7     7   6929 use integer;
  7         77  
  7         36  
6 7     7   201 use Carp qw(croak);
  7         12  
  7         567  
7 7     7   37 use vars qw($VERSION @EXPORT_OK);
  7         11  
  7         558  
8 7     7   166 use 5.006;
  7         24  
  7         304  
9              
10 7     7   35 use base qw(Exporter);
  7         11  
  7         1030  
11              
12             my @controlcifers = qw(2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1);
13              
14             $VERSION = '0.07';
15             @EXPORT_OK
16             = qw(calculate validate validatePO _argument _content _length _calculate_sum);
17              
18 7     7   38 use constant CONTROLCODE_LENGTH => 16;
  7         20  
  7         543  
19 7     7   35 use constant INVOICE_MINLENGTH => 1;
  7         12  
  7         329  
20 7     7   85 use constant INVOICE_MAXLENGTH => 15;
  7         9  
  7         305  
21 7     7   43 use constant MODULUS_OPERAND => 10;
  7         9  
  7         294  
22 7     7   32 use constant SUM_THRESHOLD => 9;
  7         7  
  7         9603  
23              
24             sub calculate {
25 17     17 1 7750 my ( $number, $maxlength, $minlength ) = @_;
26              
27 17 100       47 if ( !$minlength ) {
28 15         19 $minlength = INVOICE_MINLENGTH;
29             }
30              
31 17 100       32 if ( !$maxlength ) {
32 15         18 $maxlength = INVOICE_MAXLENGTH;
33             }
34              
35 17 100       36 if ( !$number ) {
36 1         5 _argument( $minlength, $maxlength );
37             }
38 16         30 _content($number);
39 16         28 _length( $number, $minlength, $maxlength );
40              
41 15         24 my $format = '%0' . $maxlength . 's';
42 15         46 $number = sprintf "$format", $number;
43              
44 15         27 my $sum = _calculate_sum($number);
45              
46 15         31 my $mod = $sum % MODULUS_OPERAND;
47 15         15 my $checkciffer = 0;
48              
49 15         17 $checkciffer = ( MODULUS_OPERAND - $mod );
50              
51 15         63 return ( $number . $checkciffer );
52             }
53              
54             ## no critic (RequireArgUnpacking)
55             sub validatePO {
56 8     8 1 145 return validate(@_);
57             }
58              
59             sub validate {
60 30     30 1 3025 my $controlnumber = shift;
61              
62 30 100       73 if ( !$controlnumber ) {
63 5         31 _argument(CONTROLCODE_LENGTH);
64             }
65 25         634 _content($controlnumber);
66 22         41 _length( $controlnumber, CONTROLCODE_LENGTH );
67              
68 18         38 my $sum = _calculate_sum($controlnumber);
69              
70 18 100       62 if ( $sum % MODULUS_OPERAND ) {
71 2         14 return 0;
72             } else {
73 16         62 return 1;
74             }
75             }
76              
77             sub _argument {
78 9     9   1841 my ( $length, $maxlen ) = @_;
79              
80 9 100       41 if ($maxlen) {
    100          
81 2         43 croak
82             "function takes an argument of minimum: $length and maximum $maxlen digits";
83              
84             } elsif ($length) {
85 6         103 croak "function takes an argument of $length digits";
86             } else {
87 1         32 croak "function takes an argument";
88             }
89             }
90              
91             sub _content {
92 41     41   51 my $number = shift;
93              
94 41 100       182 if ( $number !~ /^\d*$/ ) {
95 3         42 croak "argument: $number must only contain digits";
96             }
97 38         56 return 1;
98             }
99              
100             sub _length {
101 40     40   1182 my ( $number, $length, $maxlen ) = @_;
102              
103 40 100       76 if ($maxlen) {
104 18 100       61 if ( length($number) < $length ) {
    100          
105 1         13 croak "argument: $number has to be $length digits long";
106              
107             } elsif ( length($number) > $maxlen ) {
108 2         76 croak
109             "argument: $number must be not more than $maxlen digits long";
110             }
111              
112             } else {
113 22 100       64 if ( length($number) != $length ) {
114 4         52 croak "argument: $number has to be $length digits long";
115             }
116             }
117 33         48 return 1;
118             }
119              
120             sub _calculate_sum {
121 34     34   53 my $number = shift;
122              
123 34         42 my $sum = 0;
124 34         214 my @numbers = split( //, $number );
125              
126 34         127 for ( my $i = 0; $i < scalar(@numbers); $i++ ) {
127 512         483 my $tmpsum2 = 0;
128 512         799 my $tmpsum = $numbers[$i] * $controlcifers[$i];
129              
130 512 100       907 if ( $tmpsum > SUM_THRESHOLD ) {
131              
132             #TODO: address this construct
133             ## no critic (BuiltinFunctions::ProhibitVoidMap)
134 43         94 map( { $tmpsum2 += $_ } split( //, $tmpsum ) );
  86         144  
135 43         90 $tmpsum = $tmpsum2;
136             }
137 512         1363 $sum += $tmpsum;
138             }
139              
140 34         153 return $sum;
141             }
142              
143             1;
144              
145             __END__
146              
147             =head1 NAME
148              
149             Business::DK::PO - danish postal order code generator/validator
150              
151             =head1 VERSION
152              
153             This documentation describes version 0.07
154              
155             =head1 SYNOPSIS
156              
157             use Business::DK::PO qw(validate);
158              
159             my $rv;
160             eval {
161             $rv = validate(1234563891234562);
162             };
163              
164             if ($@) {
165             die "Code is not of the expected format - $@";
166             }
167              
168             if ($rv) {
169             print "Code is valid";
170             } else {
171             print "Code is not valid";
172             }
173              
174              
175             use Business::DK::PO qw(calculate);
176              
177             my $code = calculate(1234);
178              
179              
180             #Using with Params::Validate
181              
182             use Params::Validate qw(:all);
183             use Business::DK::PO qw(validatePO);
184              
185             sub check_cpr {
186             validate( @_,
187             { po =>
188             { callbacks =>
189             { 'validate_po' => sub { validatePO($_[0]); } } } } );
190              
191             print $_[1]." is a valid PO\n";
192              
193             }
194              
195             =head1 DESCRIPTION
196              
197             The postal orders and postal order codes are used by the danish postal service
198             B<PostDanmark>.
199              
200             =head1 FUNCTIONS
201              
202             =head2 validate
203              
204             The function takes a single argument, a 16 digit postal order code.
205              
206             The function returns 1 (true) in case of a valid postal order code argument and
207             0 (false) in case of an invalid postal order code argument.
208              
209             The validation function goes through the following steps.
210              
211             Validation of the argument is done using the functions (all described below in
212             detail):
213              
214             =over
215              
216             =item * _argument
217              
218             =item * _content
219              
220             =item * _length
221              
222             =back
223              
224             If the argument is a valid argument the sum is calculated by B<_calculate_sum>
225             based on the argument and the controlcifers array.
226              
227             The sum returned is checked using a modulus caluculation and based on its
228             validity either 1 or 0 is returned.
229              
230             =head2 validatePO
231              
232             A wrapper for L</validate> with a name more suitable for importing, it is less
233             common and therefor less intrusive.
234              
235             See L</validate> for details.
236              
237             =head2 calculate
238              
239             The function takes a single argument, an integer indicating a unique reference
240             number you can use to identify an order. Suggestions are invoice number,
241             order number or similar.
242              
243             The number provided must be between 1 and 15 digits long, meaning a number
244             between 1 and 999 trillions.
245              
246             The function returns a postal order code consisting of the number given as
247             argument appended with a control cifer to make the code valid (See: b<validate>
248              
249             The calculation function goes through the following steps.
250              
251             Validation of the argument is done using the functions (all described below in
252             detail):
253              
254             =over
255              
256             =item * _argument
257              
258             =item * _content
259              
260             =item * _length
261              
262             =back
263              
264             If the argument is a valid argument the sum is calculated by B<_calculate_sum>
265             based on the argument and the controlcifers array.
266              
267             Based on the sum the argument the controlcifer is calculated and appended so
268             that the argument becomes a valid postal order code.
269              
270             The calculated and valid code is then returned, left-padded with zeroes to make
271             it 16 digits long (SEE: validate).
272              
273             =head1 PRIVATE FUNCTIONS
274              
275             =head2 _argument
276              
277             This function is called from either B<validate> or B<calculate> if an argument
278             is not provided.
279              
280             It dies with an error message indicating the exceptional situation and attempts
281             to guide the user to providing a sensible input.
282              
283             The B<_argument> function takes two arguments:
284              
285             =over
286              
287             =item * minimum length required of number (mandatory)
288              
289             =item * maximum length required of number (optional)
290              
291             =back
292              
293             The arguments are used in the error message issued with B<die>, since this
294             method always dies.
295              
296             =head2 _content
297              
298             This function validates the content of the argument, it croaks if the argument
299             is not an integer (consisting of digits only).
300              
301             =head2 _length
302              
303             This function validates the length of the argument, it dies if the argument
304             does not fit wihtin the boundaries specified by the arguments provided:
305              
306             The B<_length> function takes the following arguments:
307              
308             =over
309              
310             =item * number (mandatory), the number to be validated
311              
312             =item * minimum length required of number (mandatory)
313              
314             =item * maximum length required of number (optional)
315              
316             =back
317              
318             =head2 _calculate_sum
319              
320             This function takes an integer and calculates the sum bases on the the
321             controlcifer array.
322              
323             =head1 EXPORTS
324              
325             Business::DK::PO exports on request:
326              
327             =over
328              
329             =item * L</validate>
330              
331             =item * L</validatePO>
332              
333             =item * L</calculate>
334              
335             =item * L</_argument>
336              
337             =item * L</_content>
338              
339             =item * L</_length>
340              
341             =item * L</_calculate_sum>
342              
343             =back
344              
345             =head1 TESTS
346              
347             Coverage of the test suite is at 100%
348              
349             ---------------------------- ------ ------ ------ ------ ------ ------ ------
350             File stmt bran cond sub pod time total
351             ---------------------------- ------ ------ ------ ------ ------ ------ ------
352             blib/lib/Business/DK/PO.pm 100.0 100.0 n/a 100.0 100.0 100.0 100.0
353             Total 100.0 100.0 n/a 100.0 100.0 100.0 100.0
354             ---------------------------- ------ ------ ------ ------ ------ ------ ------
355              
356             Test::Kwalitee passes
357              
358             Test::Perl::Critic passes at severity 1, brutal, with many policies disabled
359             though, see F</perlcriticrc>.
360              
361             =head1 BUGS
362              
363             Please report issues via CPAN RT:
364              
365             http://rt.cpan.org/NoAuth/Bugs.html?Dist=Business-DK-PO
366              
367             or by sending mail to
368              
369             bug-Business-DK-PO@rt.cpan.org
370              
371             =head1 SEE ALSO
372              
373             =over
374              
375             =item L<http://www.bgbank.dk/bfBlankethaandbog>
376              
377             =item bin/calculate_po.pl
378              
379             =item bin/validate_po.pl
380              
381             =back
382              
383             =head1 AUTHOR
384              
385             Jonas B. Nielsen, (jonasbn) - C<< <jonasbn@cpan.org> >>
386              
387             =head1 COPYRIGHT
388              
389             Business-DK-PO is (C) by Jonas B. Nielsen, (jonasbn) 2006-2014
390              
391             Business-DK-PO is released under the artistic license
392              
393             The distribution is licensed under the Artistic License, as specified
394             by the Artistic file in the standard perl distribution
395             (http://www.perl.com/language/misc/Artistic.html).
396              
397             =cut