#------------------------------------------------------------
# Copyright (C) 2009 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package BookPub::Tracker::File;

use strict;
use warnings;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::Client;
use Common::Util;
use RSApache::Message;
use base 'Common::FormObject';

use lib '/app/tools/bookpub/lib';
use BookPub::DB::Item::File;
use BookPub::DB::Item::Sale;
use BookPub::DB::Item::InputConversionRate;
use BookPub::DB::Item::SalePriceException;
use BookPub::Tracker::File::Display;
use BookPub::Tracker::Service::ServiceWithConfig;

my %currencies = (
    'MUL' => 'multiple currencies',
    'ARS' => 'Argentine Peso',
    'AUD' => 'Australian Dollar',
    'CAD' => 'Canadian Dollar',
    'CHF' => 'Swiss Francs',
    'CLP' => 'Chilean Peso',
    'CNY' => 'Chinese Yuan',
    'COP' => 'Columbian Peso',
    'CZK' => 'Czech Republic Koruna',
    'GBP' => 'British Pound Sterling',
    'USD' => 'United States Dollar',
    'EUR' => 'Euro',
    'ISK' => 'Icelandic Krona',
    'JMD' => 'Jamaican Dollar',
    'JPY' => 'Japanese Yen',
    'LTL' => 'Lithuanian Litai',
    'LVL' => 'Latvian Lati',
    'MXN' => 'Mexican Peso',
    'NZD' => 'New Zealand Dollar',
    'PLN' => 'Polish Zlotych',
    'QAR' => 'Qatari Riyal',
    'KRW' => 'South Korean Won',
    'SEK' => 'Swedish Krona',
    'ZAR' => 'South African Rand',
    'SIT' => 'Slovenian Tolar',
    'HRK' => 'Croatian Kuna',
    'MYR' => 'Malaysian Ringgits',
    'SGD' => 'Singapore Dollars',
    'TRY' => 'Turkey Liras',
    'EST' => 'Estonia Liras',
    'DKK' => 'Danish Krone',
    'NOK' => 'Norwegian Krone',
    'UYU' => 'Uruguayan Peso',
    'BRL' => 'Brazilian Real',
    'TWD' => 'Taiwan Dollar',
    'HKD' => 'Hong Kong Dollar',
    'RUB' => 'Russian Ruble',
    'INR' => 'Indian Rupee',
    'AED' => 'United Arab Emirates Dirham',
    'IDR' => 'Indonesian Rupiah',
    'ILS' => 'Israeli Shekel',
    'SAR' => 'Saudi Riyal',
    'HUF' => 'Hungarian Forint',
    'VEF' => 'Venezuelan Bolivar',
    'PEN' => 'Peruvian Nuevo Sol',
    'GTQ' => 'Guatemalan Quetzal',
    'BOB' => 'Bolivian Boliviano',
    'CRC' => 'Costa Rican colon',
    'PYG' => 'Paraguayan guarani',
    'NIO' => 'Nicaraguan cordoba',
    'HNL' => 'Honduran lempira',
    'BGN' => 'Bulgarian lev',
    'PHP' => 'Philippine peso',
    'THB' => 'Thai baht',
    'UAH' => 'Ukrainian hryvnia',
    'RON' => 'Romanian New Leu',
    'VND' => 'Viet Nam Dong',
    'NGN' => 'Nigerian naira',
    'KZT' => 'Kazakhstani Tenge',
    'NAD' => 'Namibian Dollar',
    'EGP' => 'Egyptian Pound',
    'UGX' => 'Ugandan Shilling',
    'PKR' => 'Pakistan Rupee',
    'DOP' => 'Dominican Peso',
    'PAB' => 'Panamanian Balboa',
    'TZS' => 'Tanzanian Shilling',
    );

sub _init {
    my ( $self, %args ) = @_;

    $self->SUPER::_init(%args);

    my %properties;
    if ( $args{dbItem} ) {
        $self->_propertiesFromDBItem( \%properties, $args{dbItem} );
    } elsif ( defined $args{fileID} ) {
        $self->_propertiesFromDB( \%properties, $args{fileID} );
    }

    my $rval = $self->_initProperties( \%properties );

    return $rval;
}

sub _propertiesFromDB {
    my ( $self, $hrProperties, $fileID ) = @_;
    assert($fileID);

    my $dbItem = BookPub::DB::Item::File->Lookup( file_id => $fileID );
    $self->_propertiesFromDBItem( $hrProperties, $dbItem );
}

sub _propertiesFromDBItem {
    my ( $self, $hrProperties, $dbItem ) = @_;
    assert($dbItem);

    # Save a reference to the dbItem we used, so we can tell later if this object
    # exists in the database already...
    #
    $self->{_dbItem} = $dbItem;

    $hrProperties->{file_id}        = $dbItem->file_id;
    $hrProperties->{service_id}     = $dbItem->service_id;
    $hrProperties->{type_id}        = $dbItem->type_id;
    $hrProperties->{version_num}    = $dbItem->version_num;
    $hrProperties->{file_dir}       = $dbItem->file_dir;
    $hrProperties->{file_name}      = $dbItem->file_name;
    $hrProperties->{orig_file_name} = $dbItem->orig_file_name;
    $hrProperties->{parent_file_id} = $dbItem->parent_file_id;
    $hrProperties->{generation}     = $dbItem->generation;
    $hrProperties->{period_id}      = $dbItem->period_id;
    $hrProperties->{file_status}    = $dbItem->file_status;
    $hrProperties->{deleted}        = $dbItem->deleted;
    $hrProperties->{import_pid}     = $dbItem->import_pid;      # <-- this should probably be a job_id now-adays.
    $hrProperties->{records}        = $dbItem->records;
    $hrProperties->{units}          = $dbItem->units;
    $hrProperties->{revenue}        = $dbItem->revenue;
    $hrProperties->{currency_code}  = $dbItem->currency_code;   # !!! I wonder about this - Should we bother since we have mixed currencies?
    $hrProperties->{total_exceptions}            = $dbItem->total_exceptions;
    $hrProperties->{remaining_exceptions}        = $dbItem->remaining_exceptions;
    $hrProperties->{price_exceptions}            = $dbItem->price_exceptions;
    $hrProperties->{price_exceptions_unapproved} = $dbItem->price_exceptions_unapproved;
    $hrProperties->{remaining_price_exceptions}  = $dbItem->remaining_price_exceptions;
    $hrProperties->{input_conversion_rate}       = $dbItem->input_conversion_rate;
    $hrProperties->{input_revenue}               = $dbItem->input_revenue;
    $hrProperties->{notes}                       = $dbItem->notes;
    $hrProperties->{errors}                      = $dbItem->errors;
    $hrProperties->{md5_sum}                     = $dbItem->file_md5sum;
    $hrProperties->{can_finish}                  = $dbItem->{can_finish};

    $hrProperties->{date_processed} = $dbItem->date_processed;
    $hrProperties->{date_created}   = $dbItem->date_created;

    $hrProperties->{date_finished} = $dbItem->date_finished;
    $hrProperties->{date_modified} = $dbItem->date_modified;
    $hrProperties->{original_service_id} = $dbItem->original_service_id;

}

sub _initProperties {
    my ( $self, $hrProperties ) = @_;

    # JPK - Because this is a 'read only' object, I am only going to use
    # 'Scalar' fields when I want to get some formatting (dates, integers, etc).
    #
    $self->{FileID}       = $hrProperties->{file_id};
    $self->{ServiceID}    = $hrProperties->{service_id};
    $self->{TypeID}       = $hrProperties->{type_id};
    $self->{VersionNum}   = $hrProperties->{version_num};
    $self->{FileDir}      = $hrProperties->{file_dir};
    $self->{FileName}     = $hrProperties->{file_name};
    $self->{OrigFileName} = $hrProperties->{orig_file_name};
    $self->{ParentFileID} = $hrProperties->{parent_file_id};
    $self->{Generation}   = $hrProperties->{generation};
    $self->{PeriodID}     = $hrProperties->{period_id};
    $self->{FileStatus}   = $hrProperties->{file_status};
    $self->{Deleted}      = $hrProperties->{deleted};
    $self->{CanFinish}    = $hrProperties->{can_finish};
    $self->{Records}      = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{records} );
    $self->{Units}        = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{units} );
    $self->{Revenue} =
      Common::FormObject::Scalar::Money->new( value => $hrProperties->{revenue}, currencyCode => $hrProperties->{currency_code} );
    $self->{CurrencyCode}        = $hrProperties->{currency_code};
    $self->{TotalExceptions}     = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{total_exceptions} );
    $self->{RemainingExceptions} = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{remaining_exceptions} );
    $self->{PriceExceptions}     = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{price_exceptions} );

    my $priceExceptionsUnapproved = $hrProperties->{price_exceptions_unapproved};
    if ( !$priceExceptionsUnapproved ) {
        $priceExceptionsUnapproved = 0;
    }

    $self->{PriceExceptionsUnapproved} = Common::FormObject::Scalar::Integer->new( value => $priceExceptionsUnapproved );
    $self->{RemainingPriceExceptions}  = Common::FormObject::Scalar::Integer->new( value => $hrProperties->{remaining_price_exceptions} );
    my $totalRemainingExceptions = $hrProperties->{remaining_exceptions} + $priceExceptionsUnapproved
      if ( defined( $hrProperties->{remaining_exceptions} ) && defined($priceExceptionsUnapproved) );
    $self->{TotalRemainingExceptions} = Common::FormObject::Scalar::Integer->new( value => $totalRemainingExceptions );
    $self->{InputConversionRate}      = $hrProperties->{input_conversion_rate};
    $self->{InputRevenue}             = Common::FormObject::Scalar::Money->new( value => $hrProperties->{input_revenue} );
    $self->{Notes}                    = $hrProperties->{notes};
    $self->{Errors}                   = $hrProperties->{errors};
    $self->{MD5Sum}                   = $hrProperties->{md5_sum};

    $self->{DateProcessed} = Common::FormObject::Scalar::Date->new( value => $hrProperties->{date_processed} );
    $self->{DateFinished}  = Common::FormObject::Scalar::Date->new( value => $hrProperties->{date_finished} );

    # !!! Going to use the 'Date' class, rather than DateTime - We don't care to display the time...
    #
    $self->{DateCreated}  = Common::FormObject::Scalar::Date->new( value => $hrProperties->{date_created} );
    $self->{DateModified} = Common::FormObject::Scalar::Date->new( value => $hrProperties->{date_modified} );

    $self->{OriginalServiceID}  = $hrProperties->{original_service_id};

    # Fetch the service name for display.
    #
    if ( $hrProperties->{service_id} ) {
        my $service = BookPub::Tracker::Service::ServiceWithConfig->new( serviceID => $hrProperties->{service_id} );
        if ($service) {
            $self->{Service} = $service;

            # !!! Templates should just look for the service object, but for now we'll be redundant.
            $self->{ServiceName} = $self->{Service}->ServiceName();
        }
    }
    $self->{ServiceName} ||= 'Unknown';

    if ( $hrProperties->{original_service_id} ) {
        my $origService = BookPub::Tracker::Service::ServiceWithConfig->new( serviceID => $hrProperties->{original_service_id} );
        $self->{OriginalServiceName} = $origService->ServiceName() if $origService;
    }

    $self->{LocaleCurrency} = Common::Client::Current()->Locale()->currencyFormat()->currencyCode();

    my $exceptions       = $hrProperties->{remaining_exceptions}       || 0;
    my $price_exceptions = $hrProperties->{remaining_price_exceptions} || 0;

    # Add in the allowable alternate country conversion codes.
    # TODO: maybe put this in table somewhere?
    #
    my @allowableCountryList = (
        'AED', 'AUD', 'CAD', 'CHF', 'CNY', 'DKK', 'EUR', 'GBP', 'HKD', 'IDR', 'ILS', 'INR',
        'JPY', 'MXN', 'NOK', 'NZD', 'RUB', 'SAR', 'SEK', 'SGD', 'TRY', 'TWD', 'USD', 'ZAR'
    );

    my @alternateCurrencyConversionList;
    foreach my $country (@allowableCountryList) {

        # Build the list of alternate currency codes, but exclude the file's currency code
        #
        push @alternateCurrencyConversionList, $country if ( $country ne $hrProperties->{currency_code} );
    }
    $self->{AlternateCurrencyConversionList}{country} = \@alternateCurrencyConversionList;

    if ( $hrProperties->{file_id} ) {

        # Scan the InputConversionRate items (if any) for this file.  If an alternate conversion
        # rate is found then make a note of it in the XML.  If the base currency is different than
        # the file's currency, and if we've found a user-inputted rate, then update the file's
        # revenue information.
        #
        my $revenue    = 0;
        my $inputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $hrProperties->{file_id} );
        if ( $inputRates->size ) {
            while ( my $item = $inputRates->next() ) {
                if ( $item->is_alternate ) {
                    my $rate = $item->conversion_rate;

                    # Just a little trick to get rid of trailing zeros.
                    $rate =~ s/\.0*$|0*$//;

                    $self->{AlternateConversionInput} = 1;
                    $self->{AlternateCurrencyCode}    = $item->currency;
                    $self->{AlternateConversionRate}  = Common::Client::Current()->Locale()->formatNumber( $item->conversion_rate ),
                      $self->{AlternateRevenueBase}   = Common::FormObject::Scalar::Money->new(
                        value        => $item->revenue * $item->conversion_rate,
                        currencyCode => $item->currency
                      );
                    $self->{AlternateNativeRate} = 1 / $item->conversion_rate;

                    next;    # Don't include alternate currency conversion into the revenue
                }

                if ( $self->{LocaleCurrency} ne $hrProperties->{currency_code} ) {
                    $revenue += $item->revenue * $item->conversion_rate;
                }
            }
        }

        # For MUL files, but can be used for all types
        $self->{AlternateCurrencyList}{Currency} = $self->getAlternateCurrencyList();

        if ( $self->{LocaleCurrency} ne $hrProperties->{currency_code} ) {
            $self->{RevenueBase} = Common::FormObject::Scalar::Money->new( value => $revenue, currencyCode => $self->{LocaleCurrency} );
        }

    }

    # If the file is open, and can be closed with no more input, set the 'QwikClose' flag.
    #
    # If this is a new file, we don't need to worry about setting the QwikClose flag.
    #
    if ( $self->FileID ) {
        if (
               BookPub::DB::Item::File::STATUS_OPEN() == $hrProperties->{file_status}
            && ( $hrProperties->{records} > ( $exceptions + $price_exceptions ) )
            && (   ( $hrProperties->{currency_code} eq $self->{LocaleCurrency} )
                || ( $hrProperties->{input_conversion_rate} && $self->conversionRatesAreSet() ) )
          ) {
            $self->{QwikClose} = 1;
        }
    }

    return $self;
}

sub save {
    my ($self) = @_;

    my $dbItem = $self->{_dbItem};
    if ( !$dbItem ) {
        $dbItem = BookPub::DB::Item::File->Create();
        $dbItem->date_created( Common::DB::Item::kDateTimeNow() );
    }

    $dbItem->service_id( $self->ServiceID );
    $dbItem->type_id( $self->TypeID );
    $dbItem->version_num( $self->VersionNum );
    $dbItem->file_dir( $self->FileDir );
    $dbItem->file_name( $self->FileName );
    $dbItem->orig_file_name( $self->OrigFileName );
    $dbItem->parent_file_id( $self->ParentFileID );
    $dbItem->generation( $self->Generation );
    $dbItem->period_id( $self->PeriodID );
    $dbItem->file_status( $self->FileStatus );
    $dbItem->deleted( $self->Deleted );
    $dbItem->records( $self->Records );
    $dbItem->units( $self->Units );
    $dbItem->revenue( $self->Revenue );
    $dbItem->currency_code( $self->CurrencyCode );
    $dbItem->total_exceptions( $self->TotalExceptions );
    $dbItem->remaining_exceptions( $self->RemainingExceptions );
    $dbItem->price_exceptions( $self->PriceExceptions );
    $dbItem->price_exceptions_unapproved( $self->PriceExceptionsUnapproved );
    $dbItem->remaining_price_exceptions( $self->RemainingPriceExceptions );
    $dbItem->input_conversion_rate( $self->InputConversionRate );
    $dbItem->input_revenue( $self->InputRevenue );
    $dbItem->notes( $self->Notes );
    $dbItem->errors( $self->Errors );
    $dbItem->file_md5sum( $self->MD5Sum );
    $dbItem->date_finished( $self->DateFinished );
    $dbItem->original_service_id( $self->OriginalServiceID );

    $dbItem->save();
    $self->_init( dbItem => $dbItem );

    return 1;
}

sub updateSummary {
    my ( $self, %args ) = @_;

    my $fileID = $self->FileID;
    return unless $fileID;

    my ( $units, $records, $revenue, $totalExceptions, $remainingExceptions, $currencyCode ) =
      BookPub::DB::Item::Sale->CalculateFileSummaryInfo( $self->FileID );

    $self->Units($units);
    $self->Records($records);
    $self->Revenue($revenue);
    $self->RemainingExceptions($remainingExceptions);
    $self->TotalExceptions($totalExceptions) if ( !defined $self->TotalExceptions );
    $self->CurrencyCode($currencyCode);
    $self->InputConversionRate(1) if ( $currencyCode && ($self->{LocaleCurrency} ne $currencyCode) || $args{altCurrency} );
}

sub delete {
    my ($self) = @_;

    if ( BookPub::DB::Item::File::STATUS_CLOSED == $self->FileStatus() ) {
        die RSApache::Message->new( type => 'error', code => 'file_not_deleted' );
    }

    # Delete the file on the server, and any associated sales (if there is a file)
    #
    my $fileID = $self->FileID();

    if ($fileID) {
        my $file = $self->FileDir . $self->FileName;
        if ( !-e $file
            || ( -f $file && unlink($file) ) ) {
            BookPub::DB::Item::Sale->DeleteByFileID($fileID);
        }
    }

    # Delete the database record.
    #
    if ( $self->{_dbItem} ) {
        $self->{_dbItem}->delete();
    }

}

sub recalculateRemainingExceptions {
    my ($self) = @_;

    my $remainingExceptions = BookPub::DB::Item::Sale->CountRemainingExceptions( $self->FileID );
    $self->RemainingExceptions($remainingExceptions);
}

sub getExceptionsFilePath {
    my ($self) = @_;
    assert( 0, 'why am I here?' );
}

sub finishImport {
    my ($self) = @_;
    my $today = Common::Util::today_and_now();

    my $newFile;
    $self->recalculateRemainingExceptions();

    my $client = Common::Client::Current();

    if ( $self->RemainingExceptions > 0
        || ( $client->isClientType(Common::Client::kSalePriceValidation) && $self->PriceExceptionsUnapproved > 0 ) ) {

        # split
        $newFile = $self->split();
        if ( !$newFile ) {
            die "Split failed";
        }
        $self->updateSummary();
    }

    $self->FileStatus(BookPub::DB::Item::File::STATUS_CLOSED);
    $self->DateFinished($today);

    $self->save();

    return $newFile;
}

sub reopen {
    my ($self) = @_;
    die "NOT PERIOD 0" unless ( 0 == $self->PeriodID() );
    die "NOT CLOSED"   unless ( BookPub::DB::Item::File::STATUS_CLOSED == $self->FileStatus() );

    $self->FileStatus(BookPub::DB::Item::File::STATUS_OPEN);
    $self->save();
}

sub split {
    my ($self) = @_;

    # Invoke the Copy constructor to make a new File record.
    #
    my $newFileDBItem = BookPub::DB::Item::File->Copy( dbItem => $self->{_dbItem} );

    my $newFile = BookPub::Tracker::File::Display->new( dbItem => $newFileDBItem );

    # Increment the generation count.
    #
    $newFile->ParentFileID( $self->FileID() );
    $newFile->Generation( $self->Generation() + 1 );

    $newFile->DateCreated( $self->DateCreated() );

    # How about file_md5sum?   That has to be unique...
    #
    $newFile->MD5Sum( Common::Util::md5sum( $newFile->FileDir . '/' . $newFile->FileName . $newFile->Generation ) );

    # Save now to get the new file_id.
    #
    $newFile->save();

    # Update the sales records with the new file id.
    #
    BookPub::DB::Item::Sale->SplitFile( $self->FileID(), $newFile->FileID() );

    # Update the summary info.
    #
    $newFile->updateSummary();

    # !!! This looks redundant to me.  updateSummary should already calculate this.
    #
    $newFile->recalculateRemainingExceptions();

    # One last wrinkle: Set the number of initial exceptions to the current number remaining.
    #
    $newFile->TotalExceptions( $newFile->RemainingExceptions() );

    # Another wrinkle: Set the number of initial price exceptions to the current number remaining.
    #
    $newFile->PriceExceptions( $newFile->RemainingPriceExceptions() );

    $newFile->save();

    # !!! Original code has logic to update the InputDistFee and InputConversionRate, which we
    # should probably re-evaluate.
    #

    if ( $self->InputConversionRate ) {
        if ( $self->CurrencyCode eq 'MUL' ) {
            my $revenue = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $self->FileID );
            assert($revenue);

            my $revenueNew = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $newFile->FileID );
            assert($revenueNew);

            # We must delete all input_conversion_rate entries for this fileID since we don't
            # know which currencies will be in each split file, but before we do, grab the
            # rate for each to use when we recreate the appropriate entries for each file
            my $inputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $self->FileID );
            assert($inputRates);

            my (%rate, %altRate);
            while ( my $item = $inputRates->next() ) {
                if ( $item->is_alternate ) {
                    $altRate{ $item->parent_currency } = {
                        rate     => $item->conversion_rate,
                        currency => $item->currency
                    };
                } else {
                    $rate{ $item->currency } = $item->conversion_rate;
                }
            }

            BookPub::DB::Item::InputConversionRate->DeleteAllByFileID( $self->FileID );

            # Recreate entries for all currencies in this file
            foreach my $currency ( keys %$revenue ) {
                # base currency rate record
                my $inputRate = BookPub::DB::Item::InputConversionRate->Create(
                    file_id         => $self->FileID,
                    currency        => $currency,
                    conversion_rate => $rate{$currency},
                    revenue         => $revenue->{$currency}
                );
                $inputRate->save();

                # alternative currency rate record
                if ( exists $altRate{$currency} ) {
                    $inputRate = BookPub::DB::Item::InputConversionRate->Create(
                        file_id         => $self->FileID,
                        currency        => $altRate{$currency}{currency},
                        conversion_rate => $altRate{$currency}{rate},
                        revenue         => $revenue->{$currency},
                        is_alternate    => 1,
                        parent_currency => $currency,
                    );
                    $inputRate->save();
                }
            }

            # Create entries for all currencies in the new file
            foreach my $currency ( keys %$revenueNew ) {
                # base currency rate record
                my $inputRate = BookPub::DB::Item::InputConversionRate->Create(
                    file_id         => $newFile->FileID,
                    currency        => $currency,
                    conversion_rate => $rate{$currency},
                    revenue         => $revenueNew->{$currency}
                );
                $inputRate->save();

                # alternative currency rate record
                if ( exists $altRate{$currency} ) {
                    $inputRate = BookPub::DB::Item::InputConversionRate->Create(
                        file_id         => $newFile->FileID,
                        currency        => $altRate{$currency}{currency},
                        conversion_rate => $altRate{$currency}{rate},
                        revenue         => $revenueNew->{$currency},
                        is_alternate    => 1,
                        parent_currency => $currency,
                    );
                    $inputRate->save();
                }
            }
        } else {

            # Just a single currency so we only need to update the current entry and create
            # a new one for the new file.
            #
            my $inputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $self->FileID );
            while ( my $item = $inputRates->next() ) {
                my $rate = $item->conversion_rate;    # grab the rate for use in the new file

                # Update the input_conversion_rate information for this file.
                # Note: updateSummary hasn't been called yet on $self so we'll do the math here.
                $item->revenue( $self->Revenue - $newFile->Revenue );
                $item->save();

                # Add an entry for the child file
                #
                my $inputRateNew = BookPub::DB::Item::InputConversionRate->Create(
                    file_id         => $newFile->FileID,
                    currency        => $item->currency,
                    conversion_rate => $rate,
                    revenue         => $newFile->Revenue,
                    is_alternate    => $item->is_alternate,
                    parent_currency => $item->parent_currency
                );
                $inputRateNew->save();
            }

        }
    }

    return $newFile;
}

sub getDateRange {
    my $self = shift;

    return BookPub::DB::Item::Sale->GetDateRangeByFileID( $self->FileID );
}

# setConversionRate: sets an input conversion rate for the file.
# Arguments:
#   rate - the conversion rate
#   currency - the currency code we're converting from (optional)
#
sub setConversionRate {
    my ($self, $rate, $currency) = @_;

    assert($rate);
    $currency ||= $self->CurrencyCode; # assume single currency

    # Set the conversion rate on all sales with the specified currency
    BookPub::DB::Item::Sale->SetConversionRateByCurrency(
        fileID => $self->FileID,
        rate => $rate,
        currency => $currency
    );

    # Create a new input conversion rate if necessary
    my $inputRate = BookPub::DB::Item::InputConversionRate->Lookup(
            file_id => $self->FileID, currency => $currency, is_alternate => 0
        )
        || BookPub::DB::Item::InputConversionRate->Create(
            file_id => $self->FileID, currency => $currency, is_alternate => 0
    );

    # Calculate the revenue to store with the input conversion rate.
    # Just sum up the revenue from the file for all sales with the same currency code.
    my $revenue = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $self->FileID, currency => $currency );
    $inputRate->revenue( $revenue->{$currency} );
    $inputRate->conversion_rate( $rate );
    $inputRate->save();

    return 1;
}

sub deleteAlternateConversionRateByFileID {
    my ($self, $fileID) = @_;

    $fileID ||= $self->FileID;

    BookPub::DB::Item::InputConversionRate->DeleteAllAlternativeByFileID($fileID);

    return 1;
}

# setAlternateConversionRate: sets an alternate input conversion rate for the file.
# Arguments:
#   rate - the conversion rate
#   currency - the currency code we're converting from
#   alternateFlag - flag indicating that we're setting an alternate currency conversion rate.
#   parentCurrency - code of the parent currency about which the alternative currency is set
#
sub setAlternateConversionRate {
    my ($self, $rate, $currency, $isAlternateRate, $parentCurrency) = @_;

    assert($rate);
    assert($parentCurrency);

    # Calculate the revenue to store with the input conversion rate.
    # As we're setting an alternate conversion rate, then the alternate currency code
    # won't exist in the sales file. In this case, just sum up the revenue across the parent
    # currency code in the file.
    my $hNativeRevenue = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $self->FileID, currency => $parentCurrency );
    my $totalRevenue = 0;
    foreach my $nativeCurrency ( sort keys %$hNativeRevenue ) {
        $totalRevenue += $hNativeRevenue->{$nativeCurrency};
    }

    # Create a new input conversion rate if necessary
    my $oInputRate = BookPub::DB::Item::InputConversionRate->Create(
        file_id => $self->FileID,
        currency => $currency,
        conversion_rate => $rate,
        revenue => $totalRevenue,
        is_alternate => $isAlternateRate,
        parent_currency => $parentCurrency,
    );

    $oInputRate->save();

    return 1;
}

sub getAlternateCurrencyList {
    my $self = shift;

    my @alternateCurrencyList;

    my $oInputRates = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $self->FileID );

    my @parentCurrencies;
    my %alternateRecords;
    while ( my $oItem = $oInputRates->next() ) {
        if ( $oItem->is_alternate ) {
            $alternateRecords{ $oItem->parent_currency } = $oItem;
            next;
        }
        push @parentCurrencies, { currency => $oItem->currency, revenue => $oItem->revenue };
    }

    unless (@parentCurrencies) {
        my $hCurrencies = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $self->FileID );
        @parentCurrencies = map { { currency => $_, revenue => $hCurrencies->{$_} } } sort keys %$hCurrencies;
    }

    foreach my $hParentCurrencyRevenue (@parentCurrencies) {
        my $parentCurrency = $hParentCurrencyRevenue->{currency};
        my $parentRevenue  = $hParentCurrencyRevenue->{revenue};

        # Default entry for the parent currency
        my %entry = (
            AlternateParentCurrencyCode    => $parentCurrency,
            AlternateCurrencyCode          => '',
            AlternateCurrencyNativeRevenue => Common::Client::Current()->Locale()->formatMoney( $parentRevenue, $parentCurrency ),
            AlternateCurrencyTotal         => '',
            AlternateCurrencyUnit          => '',
            AlternateCurrencyPercent       => '',
        );

        # An alternative currency has been set
        if ( my $oItem = $alternateRecords{$parentCurrency} ) {
            (my $conversionRate = $oItem->conversion_rate) =~ s/(\.\d*[^0])0+$/$1/;
            $conversionRate =~ s/\.0*$//;

            %entry = (
                AlternateParentCurrencyCode    => $oItem->parent_currency,
                AlternateCurrencyCode          => $oItem->currency,
                AlternateCurrencyNativeRevenue => Common::Client::Current()->Locale()->formatMoney( $oItem->revenue, $oItem->parent_currency ),
                AlternateCurrencyTotal         => $oItem->revenue * $oItem->conversion_rate,
                AlternateCurrencyUnit          => $conversionRate,
                AlternateCurrencyPercent       => 1 / $oItem->conversion_rate,
            );
        }

        push( @alternateCurrencyList, \%entry );
    }

    return \@alternateCurrencyList;
}

sub getCurrencyList {
    my $self = shift;

    my @currencyList = ();
    my $inputRates   = BookPub::DB::Item::InputConversionRate->GetAllByFileID( $self->FileID );

    # If there are user-entered conversions rates, then we'll use the currency code information
    # from the conversion rate(s) to build the currency list.  Note: this works for both
    # single- and multi-currency files because in the case of the latter, the user must enter
    # _all_ conversion rates for MUL files once they start entering rates.
    #
    # If no conversion rates have been entered, then we'll get the set of available currencies
    # from the file.
    #

    # hasInputRates: set if the user has entered non-alternate conversion rates
    #
    my $hasInputRates = 0;
    my @itemList;
    while ( my $item = $inputRates->next() ) {
        $hasInputRates++ if ( !$item->is_alternate );
        push @itemList, $item;
    }

    if ($hasInputRates) {

        # If input rates are present (not including alternate conversion rates), then get
        # the currency code information from the set of inputted rates.
        #
        foreach my $item (@itemList) {
            my %entry = (
                CurrencyCode  => $item->currency,
                CurrencyName  => $currencies{$item->currency},
                NativeRevenue => Common::Client::Current()->Locale()->formatMoney( $item->revenue, $item->currency ),
                RawRevenue    => $item->revenue,
                RevenueBase   => Common::FormObject::Scalar::Money->new(
                    value        => $item->revenue * $item->conversion_rate,
                    currencyCode => $self->{LocaleCurrency}
                ),
            );

            if ( $item->conversion_rate > 0 && !$item->is_alternate ) {
                my $rate = $item->conversion_rate;
                $rate =~ s/\.0*$|0*$//;

                $entry{Rate}         = Common::Client::Current()->Locale()->formatNumber($rate),
                  $entry{NativeRate} = 1 / $rate,
                  push( @currencyList, \%entry );
            }

        }
    } else {

        # No input rates, so get the list of currency codes from the file.
        # Note: This works for both single- and multi-currency files.
        #
        my $currencyRev = BookPub::DB::Item::Sale->GetCurrencyRevenueSummaryByFileID( fileID => $self->FileID );
        foreach my $currency ( sort keys %$currencyRev ) {
            my %entry = (
                CurrencyCode  => $currency,
                NativeRevenue => Common::Client::Current()->Locale()->formatMoney( $currencyRev->{$currency}, $currency ),
                RawRevenue    => $currencyRev->{$currency},
            );

            # in the case of multiple currency, 1 could be their preferred
            if ( $currency eq $self->{LocaleCurrency} && $self->InputConversionRate ) {
                $entry{Rate}        = 1;
                $entry{NativeRate}  = 1;
                $entry{RevenueBase} = Common::FormObject::Scalar::Money->new(
                    value        => sprintf( "%0.2f", $entry{RawRevenue} ),
                    currencyCode => $self->{LocaleCurrency}
                  ),
                  ;
            }
            push( @currencyList, \%entry );
        }
    }

    return \@currencyList;
}

# verify we have both input_conversion_rate entry(s) and non-zero rates in the sale table
sub conversionRatesAreSet {
    my $self = shift;

    my $currencies  = $self->getCurrencyList();
    my $fileRatesOK = 1;
    map { $fileRatesOK = 0 unless ( defined $_->{Rate} && $_->{Rate} > 0 ); } @$currencies;

    my $saleRatesOK = BookPub::DB::Item::Sale->ConversionRatesAreSet( $self->FileID );

    return $fileRatesOK && $saleRatesOK;
}

1;
