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

use warnings;
use strict;
use 5.010;
use Carp;
use Business::Edifact::Message::LineItem;

=head1 NAME

Business::Edifact::Message - Class that models Edifact Messages

=head1 VERSION

Version 0.07

=cut

our $VERSION = '0.07';

=head1 SYNOPSIS

Parses an individual Edifact message
Message objects are instantiated by Business::Edifact::Interchange and an array
of them is returned in its messages function
   $interchange->parse($incoming);
   my $m_array = $interchange->messages();
   for my $msg (@{$m_array}) {
      ...retrieve message data
   }

=cut

=head1 SUBROUTINES/METHODS

=head2 new

Called by Business::Edifact::Interchange to instantiate a new Message
object. The caller passes the header fields with the 
reference number identifier and message type

=cut

sub new {
    my $class      = shift;
    my $hdr_fields = shift;

    my $self = {
        ref_num            => $hdr_fields->[0]->[0],
        message_identifier => $hdr_fields->[1],
        reference          => [],
        addresses          => [],
        lines              => [],
        free_text          => [],
        segment_group      => 0,
        type               => $hdr_fields->[1]->[0],
        segment_handler    => _init_sh(),
    };

    bless $self, $class;
    return $self;
}

sub _init_sh {
    return {
        BGM => \&handle_bgm,
        DTM => \&handle_dtm,
        PAT => \&handle_pat,
        RFF => \&handle_rff,
        CUX => \&handle_cux,
        NAD => \&handle_nad,
        LIN => \&handle_lin,
        PIA => \&handle_pia,
        IMD => \&handle_imd,
        QTY => \&handle_qty,
        GIR => \&handle_gir,
        MOA => \&handle_moa,
        TAX => \&handle_tax,
        ALC => \&handle_alc,
        RTE => \&handle_rte,
        LOC => \&handle_loc,
        PRI => \&handle_pri,
        UNS => \&handle_uns,
        CNT => \&handle_cnt,
        FTX => \&handle_ftx,
        PCD => \&handle_pcd,
    };
}

=head2 add_segment

Process the next data segment

=cut

sub add_segment {
    my $self     = shift;
    my $tag      = shift;
    my $data_arr = shift;

    my $handler = $self->{segment_handler}->{$tag};
    if ($handler) {
        $self->$handler($data_arr);
    }
    return;
}

=head2 type

return the message's type
e.g. 'QUOTES' or 'ORDERS'

=cut

sub type {
    my $self = shift;
    return $self->{type};    # e.g. 'QUOTES'
}

=head2 function

Returns the message's function field
May be 'original' or 'retransmission'

=cut

sub function {
    my $self = shift;
    my $f    = $self->{bgm_data}->[2]->[0];
    if ( $f == 9 ) {
        return 'original';
    }
    elsif ( $f == 7 ) {
        return 'retransmission';
    }
    elsif ( $f == 43 ) {
        return 'additional transmission';
    }
    elsif ( $f == 46 ) {
        return 'provisional';
    }
    else {
        return $f;
    }
}

=head2 message_code

Returns 3 character message code from the BGM message

=cut

sub message_code {
    my $self = shift;
    return $self->{bgm_data}->[0]->[0];
}

=head2 currency_code

=cut

sub currency_code {
    my $self = shift;
    if ( exists $self->{currency} ) {
        return $self->{currency}->[1];
    }
    return;
}

=head2 reference_number

=cut

sub reference_number {
    my $self = shift;
    return $self->{ref_num};
}

=head2 date_of_message

=cut

sub date_of_message {
    my $self = shift;
    return $self->{message_date};
}

=head2 items

return the list of lineitems

=cut

sub items {
    my $self = shift;
    return $self->{lines};
}

=head2 handle_bgm

=cut

sub handle_bgm {
    my ( $self, $data_arr ) = @_;
    $self->{bgm_data} = $data_arr;
    return;
}

=head2 handle_dtm

NB DTM can occur in different segment groups

=cut

sub handle_dtm {
    my ( $self, $data_arr ) = @_;
    my ( $qualifier, $date, $format ) = @{ $data_arr->[0] };
    if ( length $date == 6 ) {    # century missing
        $date = "20$date";
    }
    if ( $self->{segment_group} == 0 ) {    # message header
                                            #TBD standard allows 35 repeats
        if ( $qualifier == 137 ) {
            $self->{message_date} = $date;
        }
        elsif ( $qualifier == 36 ) {
            $self->{expiry_date} = $date;
        }
        elsif ( $qualifier == 131 ) {
            $self->{tax_point_date} = $date;
        }
    }
    elsif ( $self->{segment_group} == 27 ) {
        $self->{lines}->[-1]->addsegment( 'datetimeperiod', $data_arr );
    }
    elsif ( $self->{segment_group} == 11 ) {
        $self->{payment_terms}->{payment_date} = $date;
    }
    return;
}

=head2 handle_pat

=cut

sub handle_pat {    # invoices only
    my ( $self, $data_arr ) = @_;
    if ( $data_arr->[0]->[0] == 1 ) {
        $self->{payment_terms} = {
            type  => 'basic',
            terms => $data_arr->[2],

            # terms time_reference_code|Time relation|Type of period[D = days]
            # number of periods
        };
    }
    elsif ( $data_arr->[0]->[0] == 3 ) {
        $self->{payment_terms} = { type => 'fixed_date', };
    }
    return;
}

=head2 handle_rff

=cut

sub handle_rff {
    my ( $self, $data_arr ) = @_;
    my $ref_qualifier = $data_arr->[0]->[0];
    if ( $ref_qualifier eq 'VA' ) {
        $self->{supplier_vat_number} = $data_arr->[0]->[1];
    }
    elsif ( $ref_qualifier eq 'API' ) {
        $self->{additional_party_id} = $data_arr->[0]->[1];
    }
    elsif ( $ref_qualifier eq 'FC' ) {
        $self->{fiscal_number} = $data_arr->[0]->[1];
    }
    elsif ( $ref_qualifier eq 'IA' ) {
        $self->{internal_vendor_number} = $data_arr->[0]->[1];
    }
    elsif ( $ref_qualifier eq 'TL' ) {
        $self->{tax_exemption_licence} = $data_arr->[0]->[1];
    }
    if ( $self->{segment_group} == 0 ) {
        $self->{segment_group}     = 1;    # 1 mandatory occurence
        $self->{message_reference} = {
            qualifier => $data_arr->[0]->[0],
            number    => $data_arr->[0]->[1],
        };
    }
    elsif ( $self->{segment_group} == 11 ) {    # ref to an address (SG12)
        $self->{addresses}->[-1]->{RFF} = {
            qualifier => $data_arr->[0]->[0],
            number    => $data_arr->[0]->[1],
        };

    }
    elsif ( $self->{segment_group} == 27 ) {    # ref to an address (SG12)
        $self->{lines}->[-1]->addsegment( 'item_reference', $data_arr );
    }
    elsif ( $self->{segment_group} == 25 ) {    # Buyer's orderline number
        if ( $ref_qualifier eq 'LI' ) {
            $self->{lines}->[-1]->{buyers_refnumber} = $data_arr->[0]->[1];
            if ( $data_arr->[0]->[2] ) {
                $self->{lines}->[-1]->{buyers_ref_lineno} = $data_arr->[0]->[2];
            }
        }
        else {
            $self->{lines}->[-1]->addsegment( 'item_reference', $data_arr );
        }
    }
    else {
        push @{ $self->{reference} },
          {
            qualifier => $data_arr->[0]->[0],
            number    => $data_arr->[0]->[1],
          };
    }
    return;
}

=head2 handle_cux

=cut

sub handle_cux {
    my ( $self, $data_arr ) = @_;
    if ( $self->{segment_group} == 1 ) {

        $self->{currency}      = $data_arr->[0];
        $self->{segment_group} = 4;
    }
    elsif ( $self->{segment_group} == 11 ) {
        $self->{currency} = $data_arr->[0];
    }
    return;
}

=head2 handle_nad

=cut

sub handle_nad {
    my ( $self, $data_arr ) = @_;
    push @{ $self->{addresses} }, { NAD => $data_arr, };
    $self->{segment_group} = 11;
    return;
}

=head2 handle_lin

=cut

sub handle_lin {
    my ( $self, $data_arr ) = @_;
    $self->clear_item_flags();
    if ( $self->{type} eq 'INVOIC' ) {
        $self->{segment_group} = 25;
    }
    else {
        $self->{segment_group} = 27;
    }
    my $line = {
        line_number            => $data_arr->[0]->[0],
        action_req             => $data_arr->[1]->[0],
        item_number            => $data_arr->[2]->[0],
        item_number_type       => $data_arr->[2]->[1],
        additional_product_ids => [],
        item_description       => [],
        monetary_amount        => [],
    };
    if ( $data_arr->[3]->[0] ) {
        $line->{sub_line_info} = $data_arr->[3];
    }
    my $lineitem = Business::Edifact::Message::LineItem->new($line);

    push @{ $self->{lines} }, $lineitem;
    return;
}

=head2 handle_pia

=cut

sub handle_pia {
    my ( $self, $data_arr ) = @_;
    $self->{lines}->[-1]->addsegment( 'additional_product_ids', $data_arr );
    if ( $self->{segment_group} == 25 ) {

        # For invoice may well be the item identifier
        if ( $data_arr->[0]->[0] eq '5' ) {    #alphanum as 5V is valid
            my %id_type = (
                IB => 'ISBN',
                IM => 'ISMN',
                IN => 'Purchasers_ID',
                IS => 'ISSN',
                MF => 'Manufacturers_Number',
                SA => 'Suppliers_Number',
            );
            my $t = $data_arr->[1]->[1];
            if ( exists $id_type{$t} ) {
                $t = $id_type{$t};
            }
            $self->{lines}->[-1]->{item_ID_number} = {
                number => $data_arr->[1]->[0],
                type   => $t,
            };
        }
    }
    return;
}

=head2 handle_imd

=cut

sub handle_imd {
    my ( $self, $data_arr ) = @_;
    if ( $data_arr->[0]->[0] eq 'L' ) {    # only handle text at the moment
        if ( $data_arr->[2]->[4] ) {
            $data_arr->[2]->[3] .= $data_arr->[2]->[4];
        }
        $self->{lines}->[-1]->addsegment(
            'item_description',
            {
                code => $data_arr->[1]->[0],
                text => $data_arr->[2]->[3],
            }
        );
    }
    return;
}

=head2 handle_qty

=cut

sub handle_qty {
    my ( $self, $data_arr ) = @_;
    if ( $self->type eq 'INVOIC' ) {
        my $code = $data_arr->[0]->[0];
        if ( $code == 47 ) {
            if ( $self->{segment_group} == 25 ) {    # item level
                $self->{lines}->[-1]->{quantity_invoiced} = $data_arr->[0]->[1];
            }
            else {
                $self->{quantity_invoiced} = $data_arr->[0]->[1];
            }
        }
    }
    else {
        if ( !$self->{item_locqty_flag} ) {
            $self->{lines}->[-1]->{quantity} = $data_arr->[0]->[1];
        }
        else {
            $self->{lines}->[-1]->{place_of_delivery}->[-1]->{quantity} =
              $data_arr->[0]->[1];
            delete $self->{item_locqty_flag};
        }
    }
    return;
}

=head2 handle_gir

=cut

sub handle_gir {
    my ( $self, $data_arr ) = @_;
    my $id = shift @{$data_arr};
    my $relnum = { id => $id->[0], };
    for my $d ( @{$data_arr} ) {
        push @{ $relnum->{ $d->[1] } }, $d->[0];
    }

    push @{ $self->{lines}->[-1]->{related_numbers} }, $relnum;
    return;
}

=head2 handle_moa

=cut

sub handle_moa {
    my ( $self, $data_arr ) = @_;
    if ( $self->{segment_group} == 27 || $self->{segment_group} == 25 ) {
        if ( $data_arr->[0]->[0] == 203 ) {
            $self->{lines}->[-1]->{lineitem_amount} = $data_arr->[0]->[1];
        }
        elsif ( $data_arr->[0]->[0] == 128 ) {
            $self->{lines}->[-1]->{lineitem_total_amount} =
              $data_arr->[0]->[1];
        }
        elsif ( $data_arr->[0]->[0] == 52 ) {
            $self->{lines}->[-1]->{lineitem_discount_amount} =
              $data_arr->[0]->[1];
        }
        elsif ( $data_arr->[0]->[0] == 146 ) {
            $self->{lines}->[-1]->{lineitem_unit_price} =
              $data_arr->[0]->[1];
        }
        elsif ( $self->{item_alc_flag} ) {
            $self->{lines}->[-1]->{item_allowance_or_charge}->[-1]->{amount} =
              $data_arr->[0]->[1];
        }
        else {
            my $data = shift @{$data_arr};
            my $ma   = {
                qualifier => $data->[0],
                value     => $data->[1],
            };

            push @{ $self->{lines}->[-1]->{monetary_amount} }, $ma;
        }
    }
    else {
        if ( !$self->{item_alc_flag} ) {
            my $data = shift @{$data_arr};
            my $ma   = {
                qualifier => $data->[0],
                value     => $data->[1],
            };
            push @{ $self->{monetary_amount} }, $ma;
        }
        else {
            my $data = shift @{$data_arr};
            my $ma   = {
                qualifier => $data->[0],
                value     => $data->[1],
            };
            push @{ $self->{allowance_or_charge}->[-1]->{amount} }, $ma;
            delete $self->{item_alc_flag};
        }
    }
    return;
}

=head2 handle_tax

=cut

sub handle_tax {
    my ( $self, $data_arr ) = @_;

    my $tax = {
        function_code => $data_arr->[0]->[0],
        type_code     => $data_arr->[1]->[0],
        rate          => $data_arr->[4]->[3],
        category_code => $data_arr->[5]->[0],
    };
    if ( $self->{segment_group} == 27 || $self->{segment_group} == 25 ) {
        if ( !$self->{item_alc_flag} ) {
            push @{ $self->{lines}->[-1]->{tax} }, $tax;
        }
        else {
            push
              @{ $self->{lines}->[-1]->{item_allowance_or_charge}->[-1]->{tax}
              }, $tax;
            delete $self->{item_alc_flag};
        }
    }
    else {
        push @{ $self->{tax} }, $tax;
    }
    return;
}

=head2 handle_alc

=cut

sub handle_alc {
    my ( $self, $data_arr ) = @_;
    if ( $self->{segment_group} == 27 || $self->{segment_group} == 25 ) {
        my $alc = {
            type         => $data_arr->[0]->[0],
            sequence     => $data_arr->[3]->[0],
            service_code => $data_arr->[4]->[0],
        };
        push @{ $self->{lines}->[-1]->{item_allowance_or_charge} }, $alc;
        $self->{item_alc_flag} = 1;
    }
    else {
        my $alc = {
            type         => $data_arr->[0]->[0],
            sequence     => $data_arr->[3]->[0],
            service_code => $data_arr->[4]->[0],
        };
        push @{ $self->{allowance_or_charge} }, $alc;
        $self->{item_alc_flag} = 1;
    }
    return;
}

=head2 handle_rte

=cut

sub handle_rte {
    my ( $self, $data_arr ) = @_;
    if ( $self->{type} ne 'INVOIC' ) {
        if ( $self->{item_alc_flag} == 1 ) {
            $self->{lines}->[-1]->{item_allowance_or_charge}->[-1]->{rate} =
              $data_arr->[0]->[1];
            delete $self->{item_alc_flag};
        }
    }
    return;
}

=head2 handle_loc

=cut

sub handle_loc {
    my ( $self, $data_arr ) = @_;
    if ( $self->{segment_group} == 27 && $data_arr->[0]->[0] == 7 ) {
        my $loc = {
            place     => $data_arr->[1]->[0],
            type_code => $data_arr->[1]->[2],
        };
        push @{ $self->{lines}->[-1]->{place_of_delivery} }, $loc;
        $self->{item_locqty_flag} = 1;
    }
    return;
}

=head2 handle_pri

=cut

sub handle_pri {
    my ( $self, $data_arr ) = @_;
    push @{ $self->{lines}->[-1]->{price} }, {

        # qualifier: AAA = net AAB = gross AAE/F = info
        qualifier            => $data_arr->[0]->[0],
        price                => $data_arr->[0]->[1],
        price_type           => $data_arr->[0]->[2],
        price_type_qualifier => $data_arr->[0]->[3],
    };
    return;
}

=head2 handle_uns

=cut

sub handle_uns {
    my ( $self, $data_arr ) = @_;
    $self->{segment_group} = -1;    # summary does not have a seg group
    $self->clear_item_flags();
    return;
}

=head2 handle_cnt

=cut

sub handle_cnt {
    my ( $self, $data_arr ) = @_;
    if ( $data_arr->[0]->[0] == 2 ) {
        $self->{summary_count} = $data_arr->[0]->[1];
    }
    return;
}

=head2 handle_ftx

=cut

sub handle_ftx {
    my ( $self, $data_arr ) = @_;
    my $text_field = {
        qualifier => $data_arr->[0]->[0],
        function  => $data_arr->[1]->[0],
        reference => $data_arr->[2],
    };
    if ( $data_arr->[3] ) {
        $text_field->{text} = join q{ }, @{ $data_arr->[3] };
    }
    if ( @{ $self->{lines} } ) {    # at the lineitem level
        $self->{lines}->[-1]->{free_text} = $text_field;
    }
    else {
        push @{ $self->{free_text} }, $text_field;
    }
    return;
}

=head2 handle_pcd

=cut

sub handle_pcd {
    my ( $self, $data_arr ) = @_;
    if ( $self->{item_alc_flag} ) {
        $self->{lines}->[-1]->{item_allowance_or_charge}->[-1]->{percentage} =
          $data_arr->[0]->[1];
        delete $self->{item_alc_flag};
    }
    return;

}

=head2 clear_item_flags

 clear flags at start of new item or summary

=cut

sub clear_item_flags {
    my $self = shift;
    delete $self->{item_locqty_flag};
    delete $self->{item_alc_flag};
    return;
}

=head1 AUTHOR

Colin Campbell, C<< <colinsc@cpan.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-edifact-interchange at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Business-Edifact-Interchange>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Business::Edifact::Message


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

Copyright 2011-2014 Colin Campbell.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.


=cut

1;