The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Business::DPD;

use strict;
use warnings;
use 5.010;

use version; our $VERSION = version->new('0.22');

use parent qw(Class::Accessor::Fast);
use Business::DPD::DBIC;
use Business::DPD::Label;
use Carp;
use Scalar::Util 'weaken';
use DateTime;

__PACKAGE__->mk_accessors(qw(schema schema_class dbi_connect _iso7064_mod37_36_checksum_map originator_address));

=head1 NAME

Business::DPD - handle DPD label generation

=head1 SYNOPSIS

    use Business::DPD;
    my $dpd = Business::DPD->new();
    $dpd->connect_schema;
    my $label = $dpd->generate_label({
        zip             => '12555',
        country         => 'DE',
        depot           => '1090',
        serial          => '5012345678',
        service_code    => '101',    
    });
    say $label->tracking_number;
    say $label->d_sort;

    use Business::DPD;
    my $dpd = Business::DPD->new();
    $dpd->connect_schema;
    $dpd->set_originator_address({
        name1   => 'DELICom DPD GmbH',
        street  => 'Wailandtstrasse 1',
        postal  => '63741',
        city    => 'Aschaffenburg',
        country => 'DE',
        phone   => '06021/ 0815',
        fax     => '06021/ 0816',
        email   => 'test.dpd@dpd.com',
        depot   => '0176',
    }));
    my $label = $dpd->generate_label({
        address         => Business::DPD::Address->new($dpd,{ ... });
        serial          => '5012345678',
        service_code    => '101',
    });
    say $label->tracking_number;
    say $label->d_sort;

=head1 DESCRIPTION

Calculate routing information for parcel sending via DPD (http://dpd.com)

Generate labels for parcels (including barcode)

=head1 METHODS

=head2 Public Methods

=cut

=head3 new

    my $dpd = Business::DPD->new();

Perl default, Business::DPD will use the included SQLite DB and 
C<Business::DPD::DBIC::Schema>. If you want to use another DB or 
another schema-class, you can define them via the options 
C<schema_class> and C<dbi_connect>.

    my $dpd = Business::DPD->new({
        schema_class => 'Your::Schema::DPD',
        dbi_connect  => ['dbi:Pg:dbname=yourdb','dbuser','dbpasswd', { } ],
    });

=cut

sub new {
    my ($class, $opts) = @_;

    $opts->{schema_class} ||= 'Business::DPD::DBIC::Schema';
    $opts->{dbi_connect} ||= [ 'dbi:SQLite:dbname=' . Business::DPD::DBIC->path_to_sqlite ];

    my $self = bless $opts, $class;
    return $self;
}

=head3 connect_schema

    $dpd->connect_schema;

Connect to the Schema/DB specified in L<new>.

Stores the DBIx::Class Schema in C<< $dpd->schema >>. 

=cut

sub connect_schema {
    my $self = shift;

    eval "require ".$self->schema_class;
    croak $@ if $@;

    my $schema = $self->schema_class->connect(@{$self->dbi_connect});
    $self->schema($schema);

    unless ($ENV{HARNESS_ACTIVE}) {
        my $expires = $self->routing_meta->expires;
        my $today   = DateTime->now()->strftime('%Y%m%d');
        warn 'your DPD routing database is outdated since '.$expires
            if $expires < $today;
    }
}

=head3 generate_label

    my $label = $dpd->generate_label({
        zip             => '12555',
        country         => 'DE',
        depot           => '1090',
        serial          => '5012345678',
        service_code    => '101',    
    });

=cut

sub generate_label {
    my ($self, $data) = @_;

    my $label = Business::DPD::Label->new($self, $data);
}

sub iso7064_mod37_36_checksum {
    my $self = shift;
    my $string = shift;
    my ($map, $chars) = $self->iso7064_mod37_36_checksum_map;
    
    my $m  = 36;
    my $m1 = $m + 1;
    my $p  = $m;

    foreach my $chr ( split( //, uc($string) ) ) {
        if ( defined $map->{$chr} ) {
            $p += $map->{$chr};
            $p -= $m if ( $p > $m );
            $p *= 2;
            $p -= $m1 if ( $p >= $m1 );
        }
        else {
            croak "Cannot find value for $chr";
        }
    }
    $p = $m1 - $p;
    return ( $p == $m ) ? $chars->[0] : $chars->[$p];
}

sub iso7064_mod37_36_checksum_map {
    my $self = shift;
    my @chars = ( 0 .. 9, 'A' .. 'Z', '*' );
    my $map = $self->_iso7064_mod37_36_checksum_map;
    return ($map,\@chars) if $map;

    my $count = 0;
    my %map   = ();
    for (@chars) {
        $map{$_} = $count;
        $count++;
    }
    $self->_iso7064_mod37_36_checksum_map(\%map);
    return (\%map,\@chars);
}

=head3 country_code

    my $country_num = $dpd->country_code('DE');

=cut

sub country_code {
    my ($self, $country) = @_;
    my $c = $self->schema->resultset('DpdCountry')->search({ alpha2 => $country })->first;
    croak 'country "'.$country.'" not found' unless $c;
    return $c->num;
}

=head3 country_alpha2

    my $country = $dpd->country_alpha2(276);

=cut

sub country_alpha2 {
    my ($self, $country_num) = @_;
    my $c = $self->schema->resultset('DpdCountry')->search({ num => $country_num })->first;
    croak 'country "'.$country_num.'" not found' unless $c;
    return $c->alpha2;
}

=head3 routing_meta

    my $routing_version = $dpd->routing_meta->version;

Returns L<Business::DPD::DBIC::Schema::DpdMeta> object.

=cut

sub routing_meta {
    my ($self) = @_;
    my $meta = $self->schema->resultset('DpdMeta')->search({})->single;
    croak 'no meta!' unless $meta;
    return $meta;
}

sub set_originator_address {
    my ($self, $options) = @_;
    $self->originator_address(Business::DPD::Address->new(
        $self,
        $options,
    ));

    # prevent circular reference
    weaken($self->originator_address->{_dpd});
}

1;

__END__

=head1 TO GENERATE DPD ROUTE DATABASE

    cd Business-DPD
    mkdir route-db
    cd route-db
    wget https://www.dpdportal.sk/download/routing_tables/rlatest_rev_dpdshipper_legacy.zip
    unzip rlatest_rev_dpdshipper_legacy.zip
    cd ..
    rm -f lib/Business/DPD/dpd.sqlite
    perl -Ilib helper/generate_sqlite_db.pl
    perl -Ilib helper/import_dpd_data.pl route-db/
    perl Build.PL
    perl Build test
    sudo perl Build install

=head1 AUTHOR

Thomas Klausner C<< domm AT cpan.org >>

Jozef Kutej C<< jozef@kutej.net >>

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut