File Coverage

blib/lib/Business/Colissimo.pm
Criterion Covered Total %
statement 190 257 73.9
branch 91 136 66.9
condition 45 96 46.8
subroutine 20 25 80.0
pod 21 21 100.0
total 367 535 68.6


line stmt bran cond sub pod time code
1             package Business::Colissimo;
2              
3 8     8   220570 use 5.006;
  8         32  
  8         308  
4 8     8   48 use strict;
  8         18  
  8         359  
5 8     8   43 use warnings;
  8         26  
  8         408  
6              
7 8     8   8031 use Barcode::Code128;
  8         48443  
  8         27618  
8              
9             =head1 NAME
10              
11             Business::Colissimo - Shipping labels for ColiPoste
12              
13             =head1 VERSION
14              
15             Version 0.2200
16              
17             =cut
18              
19             our $VERSION = '0.2200';
20              
21             my %product_codes = (access_f => '8L',
22             expert_f => '8V',
23             expert_om => '7A',
24             expert_i => 'CY',
25             expert_i_kpg => 'EY'
26             );
27              
28             my %test_account = (access_f => '964744',
29             expert_f => '964744',
30             expert_om => '964744',
31             expert_i => '964744',
32             expert_kpg => '900000',
33             );
34              
35             my %test_ranges = (access_f => [qw/4139207826 4139212825/],
36             expert_f => [qw/5649204247 5649209246/],
37             expert_om => [qw/5389439016 5389444015/],
38             expert_i => [qw/00005801 00055800/],
39             expert_i_kpg => [qw/00000001 00051000/],
40             );
41            
42             my %attributes = (parcel_number => 'parcel number',
43             postal_code => 'postal code',
44             customer_number => 'customer number',
45             weight => 'parcel weight',
46             not_mechanisable => 'not mechanisable',
47              
48             # expert modes
49             cod => 'cash on delivery',
50             level => 'insurance/recommendation level',
51              
52             # expert_om/expert_i modes
53             ack_receipt => 'acknowledgement of receipt',
54            
55             # expert_om mode
56             duty_free => 'customs duty free (FTD)',
57            
58             # expert_i mode
59             country_code => 'country code',
60            
61             # barcode image
62             scale => 'barcode image scale factor',
63             height => 'barcode image height',
64             padding => 'barcode image padding',
65              
66             # testing
67             test => 'testing',
68             );
69              
70             my %logo_files = (access_f => 'AccessF',
71             expert_f => 'ExpertF',
72             expert_om => 'ExpertOM',
73             expert_i => 'ExpertInter',
74             expert_i_kpg => 'ExpertInter',
75             );
76              
77             my %countries = (AT => {epg => 1},
78             AU => {kpg => 1},
79             BE => {epg => 1},
80             BR => {kpg => 1},
81             CH => {epg => 1},
82             CN => {kpg => 1},
83             DE => {epg => 1},
84             DK => {epg => 1},
85             ES => {epg => 1},
86             FI => {epg => 1},
87             FR => {epg => 1},
88             GB => {epg => 1},
89             GR => {epg => 1},
90             HK => {kpg => 1},
91             IE => {epg => 1},
92             IL => {kpg => 1},
93             IS => {epg => 1},
94             IT => {epg => 1},
95             JP => {kpg => 1},
96             KR => {kpg => 1},
97             LU => {epg => 1},
98             MA => {kpg => 1},
99             NO => {epg => 1},
100             PT => {epg => 1},
101             RU => {kpg => 1},
102             SG => {kpg => 1},
103             SE => {epg => 1},
104             VN => {kpg => 1},
105             US => {kpg => 1},
106             );
107              
108             =head1 SYNOPSIS
109              
110             use Business::Colissimo;
111              
112             $colissimo = Business::Colissimo->new(mode => 'access_f',
113             customer_number => '900001',
114             parcel_number => '2052475203',
115             postal_code => '72240',
116             weight => 120);
117              
118             # get logo file name
119             $colissimo->logo;
120              
121             # produce barcode images
122             $colissimo->barcode('tracking', spacing => 1);
123             $colissimo->barcode('shipping', spacing => 1);
124              
125             # customer number
126             $colissimo->customer_number('900001')
127             # parcel number from your alloted range numbers
128             $colissimo->parcel_number('2052475203');
129              
130             # country code for recipient (expert_i and expert_i_kpg mode)
131             $colissimo->country_code('DE');
132              
133             # postal code for recipient
134             $colissimo->postal_code('72240');
135              
136             # add weight in grams
137             $colissimo->weight(250);
138              
139             # not mechanisable option
140             $colissimo->not_mechanisable(1);
141            
142             # cash on delivery option (expert mode only)
143             $colissimo->cod(1);
144              
145             # insurance level (expert mode only)
146             $colissimo->level('01');
147              
148             # recommendation level (expert_f and expert_om mode only)
149             $colissimo->level('21');
150              
151             # set scale in pixels for barcode image (default: 1)
152             $colissimo->scale(2);
153              
154             # set height in pixels for barcode image (default: 77)
155             $colissimo->height(100);
156              
157             =head1 DESCRIPTION
158              
159             Business::Colissimo helps you to produce shipping labels
160             for the following ColiPoste services:
161              
162             =over 4
163              
164             =item Access France
165            
166             $colissimo = Business::Colissimo->new(mode => 'access_f');
167              
168             =item Expert France
169              
170             $colissimo = Business::Colissimo->new(mode => 'expert_f');
171              
172             =item Expert Outre Mer
173              
174             $colissimo = Business::Colissimo->new(mode => 'expert_om');
175              
176             =item Expert International
177              
178             KPG countries:
179              
180             $colissimo = Business::Colissimo->new(mode => 'expert_i_kpg');
181              
182             Countries outside of KPG:
183              
184             $colissimo = Business::Colissimo->new(mode => 'expert_i');
185              
186             =back
187              
188             =head1 METHODS
189              
190             =head2 new
191              
192             $colissimo = Business::Colissimo->new(mode => 'access_f',
193             customer_number => '900001',
194             parcel_number => '2052475203',
195             postal_code => '72240',
196             weight => 250);
197              
198             $colissimo = Business::Colissimo->new(mode => 'expert_f',
199             customer_number => '900001',
200             parcel_number => '2052475203',
201             postal_code => '72240',
202             weight => 250,
203             cod => 1,
204             level => '01');
205              
206             =cut
207              
208             sub new {
209 40     40 1 32743 my ($class, $self, %args);
210              
211 40         75 $class = shift;
212 40         134 %args = @_;
213              
214 40 50 33     261 unless (defined $args{mode} && $product_codes{$args{mode}}) {
215 0         0 die 'Please select valid mode for ', __PACKAGE__;
216             }
217              
218 40         443 $self = {mode => delete $args{mode},
219             parcel_number => '',
220             country_code => '',
221             postal_code => '',
222             customer_code => '',
223             not_mechanisable => '0',
224              
225             # expert
226             cod => '0',
227             level => '00',
228             ack_receipt => '0',
229             duty_free => '0',
230              
231             # barcode image
232             scale => 1,
233             height => 77,
234             padding => 20,
235              
236             # testing
237             test => 0,
238             };
239              
240 40 100 100     236 if ($self->{mode} eq 'expert_i' || $self->{mode} eq 'expert_i_kpg') {
241 13         27 $self->{international} = 1;
242             }
243             else {
244 27         71 $self->{international} = 0;
245             }
246            
247 40         86 bless $self, $class;
248              
249 40         97 for my $name (keys %args) {
250 29 50       84 if (exists $attributes{$name}) {
251 29         98 $self->$name($args{$name});
252             }
253             }
254              
255 26         90 return $self;
256             }
257              
258             =head2 barcode
259              
260             Produces the tracking barcode:
261              
262             $colissimo->barcode('tracking');
263              
264             Same with proper spacing for the shipping label:
265              
266             $colissimo->barcode('tracking', spacing => 1);
267              
268             Produces the sorting barcode:
269              
270             $colissimo->barcode('sorting');
271              
272             Same with proper spacing for the shipping label:
273              
274             $colissimo->barcode('sorting', spacing => 1);
275              
276             =cut
277              
278             sub barcode {
279 32     32 1 20538 my ($self, $type, %args) = @_;
280 32         44 my ($barcode, $parcel_number, $control);
281              
282 32         69 $barcode = $product_codes{$self->{mode}};
283              
284 32 50       89 unless (length($self->{parcel_number})) {
285 0         0 die "Missing $attributes{parcel_number}";
286             }
287            
288 32 100       73 if ($type eq 'sorting') {
289             # check if we have everything we need
290 16         42 my @required = qw/postal_code parcel_number customer_number weight/;
291              
292 16 100       38 if ($self->{international}) {
293 4         7 push (@required, 'country_code');
294             }
295            
296 16         30 for my $name (@required) {
297 68 50       193 unless (length($self->{$name})) {
298 0         0 die "Missing $attributes{$name} for mode $self->{mode}";
299             }
300             }
301            
302             # fixed sort code
303 16 100       41 if ($self->{international}) {
304 4         8 $barcode .= '2';
305             }
306             else {
307 12         24 $barcode .= '1';
308             }
309            
310 16 100       34 if ($self->{international}) {
311             # recipient country code
312 4         10 $barcode .= $self->country_code;
313              
314             # recipient postal code (first three characters, filled with zeroes if necessary)
315 4         10 my $postal = $self->postal_code;
316              
317 4 50       11 if (length($postal) >= 3) {
318 4         10 $barcode .= substr($postal, 0, 3);
319             }
320             else {
321 0         0 $barcode .= $postal . ('0' x (3 - length($postal)));
322             }
323             }
324             else {
325             # recipient postal code
326 12         28 $barcode .= $self->postal_code;
327             }
328            
329             # customer code
330 16         34 $control = $self->customer_number;
331              
332             # parcel weight
333 16         33 $control .= $self->weight;
334              
335             # insurance/recommendation level
336 16         38 $control .= $self->level;
337              
338             # not mechanisable
339 16         35 $control .= $self->not_mechanisable;
340              
341 16 100 100     61 if ($self->{mode} eq 'expert_om'
342             || $self->international) {
343             # combination of cash on delivery, customs duty free
344             # and acknowledgement of receipt options
345 12         28 $control .= $self->cod
346             + 2 * $self->duty_free
347             + 4 * $self->ack_receipt;
348             }
349             else {
350             # cash on delivery
351 4         17 $control .= $self->cod;
352             }
353              
354             # control link digit (last digit of parcel number)
355 16 100       36 if ($self->international) {
356 4         10 $control .= substr($self->parcel_number, 7, 1);
357             } else {
358 12         27 $control .= substr($self->parcel_number, 9, 1);
359             }
360            
361 16         38 $barcode .= $control . $self->control_key($control);
362            
363            
364 16 100       50 if ($args{spacing}) {
365 8         54 return join(' ', substr($barcode, 0, 3),
366             substr($barcode, 3, 5),
367             substr($barcode, 8, 6),
368             substr($barcode, 14, 4),
369             substr($barcode, 18, 6));
370             }
371             }
372             else {
373 16         34 $parcel_number = $self->parcel_number;
374 16         23 $barcode .= $parcel_number;
375 16         38 $barcode .= $self->control_key($parcel_number);
376              
377 16 100 66     58 if ($self->{international} && $type eq 'tracking') {
378 4         7 $barcode .= 'FR';
379             }
380            
381 16 100       40 if ($args{spacing}) {
382 8 100       18 if ($self->{international}) {
383 2         14 return join(' ', substr($barcode, 0, 2),
384             substr($barcode, 2, 4),
385             substr($barcode, 6, 4),
386             substr($barcode, 10, 3));
387             }
388             else {
389 6         38 return join(' ', substr($barcode, 0, 2),
390             substr($barcode, 2, 5),
391             substr($barcode, 7, 5),
392             substr($barcode, 12, 1));
393             }
394             }
395             }
396              
397 16         53 return $barcode;
398             }
399              
400             =head2 barcode_image
401              
402             Produces PNG image for tracking barcode:
403              
404             $colissimo->barcode_image('tracking');
405              
406             Produces PNG image for sorting barcode:
407              
408             $colissimo->barcode_image('sorting');
409              
410             Produces PNG image for arbitrary barcode:
411              
412             $colissimo->barcode_image('8L20524752032');
413              
414             The scale of the image can be changed for each
415             barcode individually:
416              
417             $colissimo->barcode_image('8L20524752032', scale => 2);
418              
419             The default scale is set to 1, because that produces
420             images with the right number of pixels to include them
421             into PDF with L, which uses 72dpi resolution
422             for images unless you specify width and height explicitly
423             (see L).
424              
425             The formula for calculating width in mm for a 72dpi
426             resolution is as follows:
427              
428             (1px * 25.4) / 72dpi
429              
430             This fits into Colissimo's requirement for the basic
431             module (narrowest element of the bar code) of
432             0.33 to 0.375 mm.
433              
434             =cut
435              
436             sub barcode_image {
437 0     0 1 0 my ($self, $type, %args) = @_;
438 0         0 my ($barcode, $image, $code128, $png, $scale, $height, $padding);
439              
440 0 0 0     0 if ($type eq 'tracking' || $type eq 'sorting') {
441 0         0 $barcode = $self->barcode($type);
442             }
443             else {
444 0         0 $barcode = $type;
445             }
446              
447 0         0 $code128 = Barcode::Code128->new;
448 0         0 $code128->border(0);
449              
450             # scale
451 0 0 0     0 if ($scale = $self->{scale} || $args{scale}) {
452 0         0 $code128->scale($scale);
453             }
454              
455             # height
456 0 0 0     0 if ($height = $self->{height} || $args{height}) {
457 0         0 $code128->height($height);
458             }
459              
460             # padding
461 0   0     0 $padding = $self->{padding} || $args{padding};
462 0         0 $code128->padding($padding);
463              
464 0         0 $code128->show_text(0);
465              
466 0         0 $png = $code128->png($barcode);
467             }
468              
469              
470             =head2 logo
471              
472             Returns logo file name for selected service.
473              
474             $colissimo->logo;
475              
476             =cut
477              
478             sub logo {
479 5     5 1 32 my $self = shift;
480              
481 5         22 return $logo_files{$self->{mode}} . '.bmp';
482             }
483              
484             =head2 test
485              
486             Toggles testing.
487              
488             $colissimo->test(1);
489              
490             =cut
491              
492             sub test {
493 0     0 1 0 my $self = shift;
494              
495 0 0 0     0 if (@_ > 0 && defined $_[0]) {
496 0         0 $self->{test} = $_[0];
497            
498 0 0 0     0 if ($self->{test} && ! $self->{customer_number}) {
499             # use predefined customer number for tests
500 0         0 $self->{customer_number} = $test_account{$self->{mode}};
501             }
502             }
503              
504 0         0 return $self->{test};
505             }
506              
507             =head2 scale
508              
509             Get current scale for barcode image:
510              
511             $colissimo->scale;
512              
513             Set current scale for barcode image:
514              
515             $colissimo->scale(3);
516              
517             =cut
518              
519             sub scale {
520 0     0 1 0 my $self = shift;
521 0         0 my $scale;
522              
523 0 0 0     0 if (@_ > 0 && defined $_[0]) {
524 0         0 $scale = $_[0];
525              
526 0 0       0 if ($scale !~ /^\d+$/) {
527 0         0 die 'Please provide valid scale factor';
528             }
529              
530 0         0 $self->{scale} = $scale;
531             }
532              
533 0         0 return $self->{scale};
534             }
535              
536             =head2 height
537              
538             Get current height for barcode image:
539              
540             $colissimo->height;
541              
542             Set current height for barcode image:
543              
544             $colissimo->height(100);
545              
546             =cut
547              
548             sub height {
549 0     0 1 0 my $self = shift;
550 0         0 my $height;
551              
552 0 0 0     0 if (@_ > 0 && defined $_[0]) {
553 0         0 $height = $_[0];
554              
555 0 0       0 if ($height !~ /^\d+$/) {
556 0         0 die 'Please provide valid height';
557             }
558              
559 0         0 $self->{height} = $height;
560             }
561              
562 0         0 return $self->{height};
563             }
564              
565             =head2 padding
566              
567             Get current padding for barcode image:
568              
569             $colissimo->padding;
570              
571             Set current padding for barcode image:
572              
573             $colissimo->padding(0);
574              
575             =cut
576              
577             sub padding {
578 0     0 1 0 my $self = shift;
579 0         0 my $padding;
580              
581 0 0 0     0 if (@_ > 0 && defined $_[0]) {
582 0         0 $padding = $_[0];
583              
584 0 0       0 if ($padding !~ /^\d+$/) {
585 0         0 die 'Please provide valid padding';
586             }
587              
588 0         0 $self->{padding} = $padding;
589             }
590              
591 0         0 return $self->{padding};
592             }
593              
594             =head2 customer_number
595              
596             Get current customer number:
597              
598             $colissimo->customer_number;
599              
600             Set current customer number:
601              
602             $colissimo->customer_number('900001');
603              
604             =cut
605              
606             sub customer_number {
607 24     24 1 49 my $self = shift;
608 24         27 my $number;
609              
610 24 100 66     95 if (@_ > 0 && defined $_[0]) {
611 8         10 $number = $_[0];
612            
613 8         15 $number =~ s/\s+//g;
614              
615 8 50       25 if ($number !~ /^\d{6}$/) {
616 0         0 die 'Please provide valid customer number (6 digits) for barcode';
617             }
618              
619 8         32 $self->{customer_number} = $number;
620             }
621              
622 24         52 return $self->{customer_number};
623             }
624              
625             =head2 parcel_number
626              
627             Get current parcel number:
628              
629             $colissimo->parcel_number;
630            
631             Set current parcel number:
632              
633             $colissimo->parcel_number('2052475203');
634              
635             =cut
636              
637             sub parcel_number {
638 40     40 1 4109 my $self = shift;
639 40         47 my $number;
640              
641 40 100 66     133 if (@_ > 0 && defined $_[0]) {
642 8         13 $number = $_[0];
643            
644 8         20 $number =~ s/\s+//g;
645              
646 8 100       20 if ($self->{international}) {
647 2 50       12 if ($number !~ /^\d{8}$/) {
648 0         0 die 'Please provide valid parcel number (8 digits) for barcode';
649             }
650             }
651             else {
652 6 50       25 if ($number !~ /^\d{10}$/) {
653 0         0 die 'Please provide valid parcel number (10 digits) for barcode';
654             }
655             }
656            
657 8         17 $self->{parcel_number} = $number;
658             }
659              
660 40         113 return $self->{parcel_number};
661             }
662              
663             =head2 country_code
664              
665             Get current country code:
666              
667             $colissimo->country
668              
669             Set current country code:
670              
671             $colissimo->country('BE');
672              
673             The country code is defined in the ISO 3166 standard.
674              
675             Switches to expert_i_kpg mode automatically.
676              
677             =cut
678              
679             sub country_code {
680 10     10 1 20 my $self = shift;
681 10         15 my $string;
682              
683 10 100 66     54 if (@_ > 0 && defined $_[0]) {
684 6         13 $string = uc($_[0]);
685 6         15 $string =~ s/\s+//g;
686              
687 6 50       38 if ($string !~ /^[A-Z]{2}$/) {
688 0         0 die 'Please provide valid country code for barcode';
689             }
690              
691 6 100 66     60 if ($self->{mode} eq 'access_f'
    50          
    50          
692             || $self->{mode} eq 'expert_f') {
693 1 50       5 if ($string ne 'FR') {
694 0         0 die "Only France is allowed as delivery country for $self->{mode}.\n";
695             }
696             }
697             elsif ($self->{mode} eq 'expert_om') {
698             # no country code requirements yet
699             }
700             elsif ($string eq 'FR') {
701 0         0 die "France isn't allowed as delivery country for $self->{mode}.\n";
702             }
703              
704 6 100 100     39 if (exists $countries{$string} && $countries{$string}->{kpg}) {
705 1         8 $self->{mode} = 'expert_i_kpg';
706             }
707            
708 6         13 $self->{country_code} = $string;
709             }
710              
711 10         28 return $self->{country_code};
712             }
713              
714             =head2 postal_code
715              
716             Get current postal code:
717              
718             $colissimo->postal_code
719              
720             Set current postal code:
721              
722             $colissimo->postal_code('72240');
723              
724             =cut
725              
726             sub postal_code {
727 41     41 1 74 my $self = shift;
728 41         48 my $string;
729              
730 41 100 66     161 if (@_ > 0 && defined $_[0]) {
731 25         34 $string = $_[0];
732            
733 25         48 $string =~ s/\s+//g;
734              
735 25 100       62 if ($self->{international}) {
736 7 100 100     60 if ($string eq '0' || $string !~ /^[A-Z0-9]{1,10}$/) {
737 2         21 die 'Please provide valid postal code (1-10 alphanumerics) for barcode';
738             }
739             }
740             else {
741 18 100       59 if ($string !~ /^[A-Z0-9]{5}$/) {
742 9         101 die 'Please provide valid postal code (5 alphanumerics) for barcode';
743             }
744             }
745            
746 14         29 $self->{postal_code} = $string;
747             }
748              
749 30         77 return $self->{postal_code};
750             }
751              
752             =head2 weight
753              
754             Get current weight:
755              
756             $colissimo->weight;
757              
758             Set weight in grams:
759              
760             $colissimo->weight(250);
761              
762             =cut
763              
764             sub weight {
765 24     24 1 50 my $self = shift;
766 24         48 my $number;
767              
768 24 100 66     72 if (@_ > 0 && defined $_[0]) {
769 8         15 $number = $_[0];
770            
771 8         18 $number =~ s/\s+//g;
772              
773 8 50       31 if ($number !~ /^\d{1,5}$/) {
774 0         0 die 'Please provide valid parcel weight (less than 100 kg) for barcode';
775             }
776              
777 8         51 $self->{weight} = sprintf('%04d', int($number / 10));
778             }
779              
780 24         58 return $self->{weight};
781             }
782              
783             =head2 not_mechanisable
784              
785             Get current value of not mechanisable option:
786              
787             $colissimo->not_mechanisable;
788              
789             Set current value of not mechanisable option:
790              
791             $colissimo->not_mechanisable(1);
792              
793             Possible values are 0 (No) and 1 (Yes).
794              
795             =cut
796              
797             sub not_mechanisable {
798 16     16 1 19 my $self = shift;
799 16         18 my $number;
800              
801 16 50 33     42 if (@_ > 0 && defined $_[0]) {
802 0         0 $number = $_[0];
803            
804 0         0 $number =~ s/\s+//g;
805              
806 0 0       0 if ($number !~ /^[01]$/) {
807 0         0 die 'Please provide valid value for not mechanisable (0 or 1)';
808             }
809              
810 0         0 $self->{not_mechanisable} = $number;
811             }
812              
813 16         33 return $self->{not_mechanisable};
814             }
815              
816             =head2 cod
817              
818             Get current value of cash on delivery option:
819              
820             $colissimo->cod;
821              
822             Set current value of cash on delivery option:
823              
824             $colissimo->cod(1);
825              
826             The cash on delivery option is available only in export mode,
827             possible values are 0 (No) and 1 (Yes).
828              
829             =cut
830              
831             sub cod {
832 17     17 1 22 my $self = shift;
833 17         23 my $number;
834              
835 17 100 66     49 if (@_ > 0 && defined $_[0]) {
836 1         2 $number = $_[0];
837            
838 1         13 $number =~ s/\s+//g;
839              
840 1 50       6 if ($number !~ /^[01]$/) {
841 0         0 die 'Please provide valid value for cash on delivery option (0 or 1)';
842             }
843              
844 1 50 33     6 if ($self->{mode} eq 'access'
845             && $number eq '1') {
846 0         0 die 'Cash on delivery option not available in access mode.';
847             }
848              
849 1         3 $self->{cod} = $number;
850             }
851              
852 17         52 return $self->{cod};
853             }
854              
855             =head2 level
856              
857             Get current insurance resp. recommendation level:
858              
859             $colissimo->level;
860              
861             Set current insurance resp. recommendation level:
862              
863             $colissimo->level('O1');
864             $colissimo->level('21');
865              
866             The level option is only available in expert mode,
867             possible values are 01 ... 10 for insurance level
868             and 21 ... 23 for recommendation level.
869              
870             =cut
871              
872             sub level {
873 20     20 1 30 my $self = shift;
874 20         23 my $number;
875              
876 20 100 66     58 if (@_ > 0 && defined $_[0]) {
877 4         5 $number = $_[0];
878            
879 4         7 $number =~ s/\s+//g;
880              
881 4 100       17 if ($number !~ /^(0\d|10|2[123])$/ ) {
882 1         20 die 'Please provide valid value for insurance/recommendation level.';
883             }
884              
885 3 100 66     19 if ($self->{mode} eq 'access_f'
886             && $number ne '00') {
887 2         20 die 'Insurance/recommendation level not available in access mode.';
888             }
889              
890 1         2 $self->{level} = $number;
891             }
892              
893 17         42 return $self->{level};
894             }
895              
896             =head2 ack_receipt
897              
898             Get current value for acknowledgement of receipt (AR):
899              
900             $colissimo->ack_receipt;
901              
902             Set current value for acknowledgement of receipt (AR):
903              
904             $colissimo->ack_receipt(1);
905              
906             Returns 1 if acknowledgement of receipt is enabled, 0 otherwise.
907              
908             The ack_receipt option is only available in expert_om and expert_i modes,
909             possible values are 0 (No) and 1 (Yes).
910              
911             =cut
912              
913             sub ack_receipt {
914 14     14 1 18 my $self = shift;
915 14         16 my $number;
916              
917 14 100 66     44 if (@_ > 0 && defined $_[0]) {
918 2         4 $number = $_[0];
919 2         5 $number =~ s/\s+//g;
920              
921 2 50       7 if ($number !~ /^[01]$/) {
922 0         0 die 'Please provide valid value for acknowledgement of receipt option (0 or 1)';
923             }
924              
925 2 50 33     10 unless ($number == 0 || $self->international || $self->{mode} eq 'expert_om') {
      33        
926 0         0 die 'Acknowledgement of receipt option only available in expert_om and expert_i modes.';
927             }
928            
929 2         7 $self->{ack_receipt} = $number;
930             }
931              
932 14         40 return $self->{ack_receipt};
933             }
934              
935             =head2 duty_free
936              
937             Get current value for customs duty free (FTD):
938              
939             $colissimo->duty_free;
940              
941             Set current value for customs duty free (FTD):
942              
943             $colissimo->duty_free(1);
944              
945             The custom duty free option is only available in expert_om mode,
946             possible values are 0 (No) and 1 (Yes).
947              
948             =cut
949              
950             sub duty_free {
951 13     13 1 18 my $self = shift;
952 13         15 my $number;
953              
954 13 100 66     45 if (@_ > 0 && defined $_[0]) {
955 1         2 $number = $_[0];
956 1         5 $number =~ s/\s+//g;
957              
958 1 50       6 if ($number !~ /^[01]$/) {
959 0         0 die 'Please provide valid value for customs duty free option (0 or 1)';
960             }
961              
962 1 50 33     9 unless ($number == 0 || $self->{mode} eq 'expert_om') {
963 0         0 die 'Customs duty free option only available in expert_om mode.';
964             }
965            
966 1         3 $self->{duty_free} = $number;
967             }
968            
969 13         54 return $self->{duty_free};
970             }
971              
972             =head2 international
973              
974             Returns 1 on international (expert_i or expert_i_kpg) shippings,
975             0 otherwise.
976              
977             =cut
978              
979             sub international {
980 75     75 1 341 return $_[0]->{international};
981             }
982              
983             =head2 organisation
984              
985             Returns the acronym of the inter-postal organisation (KPG or EPG)
986             corresponding to the destination country.
987              
988             =cut
989              
990             sub organisation {
991 4     4 1 29 my $self = shift;
992            
993 4 100       13 if (exists $countries{$self->{country_code}}) {
994 3         6 my $cref = $countries{$self->{country_code}};
995              
996 3 100       9 if ($cref->{epg}) {
997 2         7 return 'EPG';
998             }
999              
1000 1 50       4 if ($cref->{kpg}) {
1001 1         3 return 'KPG';
1002             }
1003             }
1004              
1005 1         4 return '';
1006             }
1007              
1008             =head2 control_key
1009              
1010             Returns control key for barcode.
1011              
1012             =cut
1013              
1014             sub control_key {
1015 41     41 1 3001 my ($self, $characters) = @_;
1016 41         53 my (@codes, $even, $odd, $key, $mod);
1017              
1018 41         281 @codes = split(//, $characters);
1019              
1020 41 100 100     102 if ($self->international && @codes == 8) {
1021             # special case for tracking control keys
1022             # for international orders
1023 8         20 my @coefficients = (8, 6, 4, 2, 3, 5, 9, 7);
1024              
1025 8         27 while (@codes) {
1026 64         152 $key += shift(@codes) * shift(@coefficients);
1027             }
1028              
1029 8         12 $mod = $key % 11;
1030              
1031 8 100       32 if ($mod == 0) {
    100          
1032 1         3 return 5;
1033             }
1034             elsif ($mod == 1) {
1035 1         4 return 0;
1036             }
1037             else {
1038 6         20 return 11 - $mod;
1039             }
1040             }
1041            
1042 33 100       92 if (@codes % 2) {
1043             # pad characters for sorting control key
1044 18         44 unshift (@codes, '0');
1045             }
1046              
1047 33         75 while (@codes) {
1048 219         314 $odd += shift(@codes);
1049 219         560 $even += shift(@codes);
1050             }
1051              
1052 33         53 $key = (3 * $even) + $odd;
1053 33         42 $mod = $key % 10;
1054              
1055 33 100       132 return $mod ? 10 - $mod : 0;
1056             }
1057              
1058             =head1 AUTHOR
1059              
1060             Stefan Hornburg (Racke), C<< >>
1061              
1062             =head1 BUGS
1063              
1064             Please report any bugs or feature requests to C, or through
1065             the web interface at L. I will be notified, and then you'll
1066             automatically be notified of progress on your bug as I make changes.
1067              
1068              
1069              
1070              
1071             =head1 SUPPORT
1072              
1073             You can find documentation for this module with the perldoc command.
1074              
1075             perldoc Business::Colissimo
1076              
1077              
1078             You can also look for information at:
1079              
1080             =over 4
1081              
1082             =item * RT: CPAN's request tracker (report bugs here)
1083              
1084             L
1085              
1086             =item * AnnoCPAN: Annotated CPAN documentation
1087              
1088             L
1089              
1090             =item * CPAN Ratings
1091              
1092             L
1093              
1094             =item * Search CPAN
1095              
1096             L
1097              
1098             =back
1099              
1100              
1101             =head1 ACKNOWLEDGEMENTS
1102              
1103             Thanks to Ton Verhagen for being a big supporter of my projects in all aspects.
1104              
1105             =head1 LICENSE AND COPYRIGHT
1106              
1107             Copyright 2011-2012 Stefan Hornburg (Racke).
1108              
1109             This program is free software; you can redistribute it and/or modify it
1110             under the terms of either: the GNU General Public License as published
1111             by the Free Software Foundation; or the Artistic License.
1112              
1113             See http://dev.perl.org/licenses/ for more information.
1114              
1115              
1116             =cut
1117              
1118             1; # End of Business::Colissimo