#!/usr/bin/perl
#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
use strict;

use Data::Dumper;
use Getopt::Std;
use POSIX qw(ceil);
use Carp;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::RSMath;

use lib '/app/tools/rps/lib';
use RPS::Statement::Mechanical::PDF;
use RPS::Statement::Mechanical::Text;
use RPS::Mechanical::MechanicalRun;
use RPS::DB::Item::MechanicalRun;
use RPS::DB::Item::Product;
use RPS::DB::Item::Publisher;
use RPS::DB::Item::PublisherAccount;
use RPS::DB::Item::Track;
use RPS::DB::Item::TrackLicense;
use RPS::DB::Item::LicenseReserve;
use RPS::DB::Item::LicenseReserveRun;
use RPS::DB::Item::ReserveLiquidation;
use RPS::DB::Item::PendingTransaction;
use RPS::DB::Item::MechanicalStatement;
use RPS::DB::Item::MechanicalStatementTransaction;
use RPS::DB::Item::MechanicalStatementLicenseTransaction;
use RPS::DB::Item::PublisherTrackCrossedLicenseAccount;
use RPS::DB::Item::MechanicalStatementCrossedLicenseTransaction;
use RPS::DB::Item::MechanicalStatementItem;
use RPS::DB::Item::MechanicalCarryover;
use RPS::DB::Item::MechanicalRunCarryover;
use RPS::DB::Item::MechanicalStatementLicense;
use RPS::DB::Item::MechanicalStatementTrack;
use RPS::DB::Item::SaleRunMap;

#use RPS::DB::Item::SaleMechanicalStatementItemMap;
use RPS::DB::Item::FinanceAccount;
use RPS::DB::Item::FinanceTransaction;
use RPS::Mechanical::Job::CreatePDFStatements;
use RPS::Mechanical::Job::CreateTextStatements;
use RPS::RoyaltyRun::Status;

use lib '/app/tools/raptor/lib';
use Raptor::DB::Item::Sale;

my $gVerbosityLevel = 1;
my $gRunLabel;

# Get the id from the command-line
#
my %options;
_parseCommandLine( \%options );
_commitRun( $options{clientID}, $options{runID} );

sub _parseCommandLine {
    my ($settings) = @_;

    my %opt;
    getopts( 'r:c:V:', \%opt );

    if ( !$opt{r} || !$opt{c} ) {
        _usage();
        exit(1);
    }
    $settings->{runID}    = $opt{r};
    $settings->{clientID} = $opt{c};

    if ( defined $opt{V} ) {
        $gVerbosityLevel = $opt{V};
    }
}

sub _usage {
    print "\nusage: $0 -c <client_id> -r <mechanical_run_id>\n";
    print "\n";
    print "Arguments:\n";
    print "\t-c <client_id>\t\tThe client_id of the client to process\n";
    print "\t-r <mechanical_run_id>\t\tThe mechanical_run_id to commit\n";
}

sub _commitRun {
    my ( $clientID, $runID ) = @_;

    # Instantiate the application singleton object.
    #
    my $appSingleton = Common::RSApp->new( clientID => $clientID );

    # Get the run state, and make sure it isn't already committed.
    #
    my $run = RPS::DB::Item::MechanicalRun->Lookup( mechanical_run_id => $runID );
    die "mechanical run $runID does not exist!" unless $run;

    if ( RPS::RoyaltyRun::Status::kRunning == $run->status ) {
        die "mechanical run $runID has not completed";
    }

    if ( RPS::RoyaltyRun::Status::kCommitted == $run->status ) {
        die "mechanical run $runID has already been committed";
    }

    if ( RPS::RoyaltyRun::Status::kClosed == $run->status ) {
        die "mechanical run $runID has already been closed";
    }

    _report("*** Committing Mechanical Royalty Run $runID ***\n");

    # Need to generate the 'name' of this run, which we use for transaction memos.
    #
    $gRunLabel = RPS::Mechanical::MechanicalRun::GenerateFileLabel( $run->start_date, $run->end_date );

    # Before we start messing with any data, we need to do some pre-flight sanity checks.
    #
    if ( !_preflightCheck($runID) ) {
        _report("*** preflight checks FAILED, exiting\n");
        return 1;
    }

    # Get all the tracks and albums that have unallocated shares.
    # We'll need this data later to decide which sales we can finalize.
    #
    # (XXX) THIS method will probably need to be modified.
    my %unallocatedTracks;
    my %unallocatedAlbums;
    _getUnallocatedTracksAndAlbums( \%unallocatedTracks, \%unallocatedAlbums );

    # Truncate the carryover table.
    #
    my $runCarryoverIDs = RPS::DB::Item::MechanicalRunCarryover->GetByMechanicalRunID($runID);
    while ( my $carryoverMapItem = $runCarryoverIDs->next() ) {
        my $carryoverID = $carryoverMapItem->mechanical_carryover_id;
        my $carryover = RPS::DB::Item::MechanicalCarryover->Lookup( mechanical_carryover_id => $carryoverID );
        $carryover->delete();
    }

    # Get rid of entries in the carryover run map
    #
    RPS::DB::Item::MechanicalRunCarryover->DeleteByMechanicalRunID($runID);

    # Fetch all the statements in this run, and commit them.
    #
    my $statements = RPS::DB::Item::MechanicalStatement->GetByMechanicalRunID($runID);

    while ( $statements->hasNext() ) {
        my $statement = $statements->next();
        _commitStatement( $statement, \%unallocatedTracks, \%unallocatedAlbums );
    }

    # Handle the license reserves
    #
    _handleReserves($runID);

    # Mark this run as 'committed'
    #
    _report("setting run status to committed");
    $run->status(RPS::RoyaltyRun::Status::kCommitted);
    $run->save();

    # Mark any previous 'committed' runs as 'closed', if they have the same payor profile.
    #
    my $payorIDProfile = _getRunPayorIDProfile($runID);

    my $allRuns = RPS::DB::Item::MechanicalRun->GetAll();
    while ( my $oldRun = $allRuns->next() ) {
        next if ( $runID == $oldRun->mechanical_run_id );
        next unless ( RPS::RoyaltyRun::Status::kCommitted == $oldRun->status() );

        my $oldProfile = _getRunPayorIDProfile( $oldRun->mechanical_run_id );
        if ( $oldProfile eq $payorIDProfile ) {
            _report( "setting status of previous run " . $oldRun->mechanical_run_id . " to Closed" );
            $oldRun->status(RPS::RoyaltyRun::Status::kClosed);
            $oldRun->save();
        }
    }

    # Re-create the pdf statements to remove the 'DRAFT' watermark.
    #
    _report("*** adding PDF statements job to queue");
    my $statementsPath = RPS::Statement::Mechanical::PDF::StatementPathFromRunID( $runID, $clientID );
    if ( -d $statementsPath ) {
        system("rm -rf $statementsPath");
    }
    my $jobArgs = RPS::Mechanical::Job::CreatePDFStatements->new(
        runID    => $runID,
        clientID => $clientID,
        filePath => $statementsPath,
    );
    my $job = $jobArgs->enqueue();
    _report("*** done adding PDF statements job to queue!");

    _report("*** adding Text statements job to queue");
    $statementsPath = RPS::Statement::Mechanical::Text::StatementPathFromRunID( $runID, $clientID );
    $jobArgs = RPS::Mechanical::Job::CreateTextStatements->new(
        runID    => $runID,
        clientID => $clientID,
        filePath => $statementsPath,
    );
    $job = $jobArgs->enqueue();
    _report("*** done adding Text statements job to queue!");

    _report("done");
}

sub _getRunPayorIDProfile {
    my ($runID) = @_;

    my @payorIDs = RPS::DB::Item::MechanicalStatement->GetDistinctPayorsByRunID($runID);
    @payorIDs = sort @payorIDs;
    my $profile = join( ',', @payorIDs );
    return $profile;
}

sub _commitStatement {
    my ( $statement, $unallocatedTracks, $unallocatedAlbums ) = @_;

    my $statementID = $statement->mechanical_statement_id;

    _report("\n------------------------------------");

    # We don't commit 'unknown publisher' statements.
    #
    if ( 0 == $statement->publisher_id ) {
        _report( "... skipping statement $statementID, which belongs to the unknown publisher", 2 );
        return;
    }
    _report("committing statement id $statementID");

    # Commit advances at the license level.
    #
    my $licenseItems = RPS::DB::Item::MechanicalStatementLicense->GetByMechanicalStatementID($statementID);
    while ( $licenseItems->hasNext() ) {
        my $licenseItem = $licenseItems->next();

        _applyLicenseAdvance( $licenseItem, $statementID );
    }

    # Need to apply pending transactions and such to the crossed license accounts for each track.
    #
    my $allTrackItems = RPS::DB::Item::MechanicalStatementTrack->GetByMechanicalStatementID($statementID);
    while ( my $trackItem = $allTrackItems->next() ) {
        _applyCrossedLicenseAdvance( $trackItem, $statement );
    }

    # Commit advances at the publisher level.
    #
    _applyPublisherAdvance($statement);

    # Handle the 'holdover' account.
    # !!! Really not necessary anymore - these two accounts have been combined.
    #
    #    _handleHoldover($statement);

    # Handle carryover units
    #
    _handleCarryover($statement);

}

sub _handleReserves {
    my ($runID) = @_;

    # 'turn the crank' on the reserves table.
    #
    my $runReserves = RPS::DB::Item::LicenseReserveRun->GetByMechanicalRunID($runID);
    while ( my $runReserveItem = $runReserves->next() ) {
        my $reserve = RPS::DB::Item::LicenseReserve->Lookup( license_reserve_id => $runReserveItem->license_reserve_id );

        if ( !$reserve ) {

            # This should never happen!
            #
            _report( "ERROR!  reserve id " . $runReserveItem->license_reserve_id . " is MISSING" );
            next;
        }
        my $period = $reserve->periods_remaining();

        if ( 1 == $period ) {

            # This reserve has been liquidated;
            # So we'll make note of the run id.
            #
            $reserve->liquidated_run_id($runID);
        }

        # Just decrement the periods_remaining column.
        # Liquidated reserves will end up with '0' periods_remaining.
        #
        $reserve->periods_remaining( $period - 1 );
        $reserve->save();
    }
}

sub _handleCarryover {
    my ($statement) = @_;

    my $statementItems = RPS::DB::Item::MechanicalStatementItem->GetByMechanicalStatementID( $statement->mechanical_statement_id );
    while ( my $item = $statementItems->next() ) {
        if ( $item->carryover ) {

            my $newCarryover = RPS::DB::Item::MechanicalCarryover->Create(
                track_license_id   => $item->track_license_id,
                sale_stat_rate_id  => $item->sale_stat_rate_id,
                issue_stat_rate_id => $item->issue_stat_rate_id,
                product_id         => $item->product_id,
                units              => $item->carryover,
            );

            $newCarryover->save();
            _report(
                "   created carryover: "
                  . $newCarryover->mechanical_carryover_id . " "
                  . $item->track_license_id . " "
                  . $item->sale_stat_rate_id . " "
                  . $item->issue_stat_rate_id . " "
                  . $item->product_id . " "
                  . $item->carryover,
                2
            );
        }
    }
}

sub _applyCrossedLicenseAdvance {
    my ( $trackItem, $statement ) = @_;

    # Is there any activity?
    #
    if (   0 != $trackItem->crossed_previous_advance_balance
        || 0 != $trackItem->crossed_transaction_subtotal
        || 0 != $trackItem->crossed_advance_balance ) {

        # See if we need to create a new account and mapping.
        #
        my $publisherID = $trackItem->publisher_id;
        my $trackID     = $trackItem->track_id;
        my $payorID     = $statement->payor_id;
        my $statementID = $statement->mechanical_statement_id;

        my $accountID;
        my $crossedAccountMap = RPS::DB::Item::PublisherTrackCrossedLicenseAccount->Lookup(
            publisher_id => $trackItem->publisher_id,
            track_id     => $trackItem->track_id,
            payor_id     => $statement->payor_id,
        );

        if ($crossedAccountMap) {
            $accountID = $crossedAccountMap->finance_account_id;
        }

        if ( !$accountID ) {
            my $account = RPS::DB::Item::FinanceAccount->Create(
                description => "account for crossed licenses, publisher $publisherID, track $trackID, payor $payorID",
                type_code   => RPS::DB::Item::FinanceAccount::kAccountTypeAdvance,

                # !!! Will need to account for currency_code
                currency_code => 'USD',
            );
            $account->save();

            $accountID = $account->finance_account_id;

            # Create the map if necessary.
            #
            if ( !$crossedAccountMap ) {
                $crossedAccountMap = RPS::DB::Item::PublisherTrackCrossedLicenseAccount->Create(
                    publisher_id       => $trackItem->publisher_id,
                    track_id           => $trackItem->track_id,
                    payor_id           => $statement->payor_id,
                    finance_account_id => $accountID,
                );
            } else {
                $crossedAccountMap->finance_account_id($accountID);
            }
            $crossedAccountMap->save();
        }

        # Now apply all pending transactions, and delete them.
        #
        my $transactionItems =
          RPS::DB::Item::MechanicalStatementCrossedLicenseTransaction->GetByMechanicalStatementIDPublisherIDTrackID( $statementID,
            $publisherID, $trackID );

        while ( my $item = $transactionItems->next() ) {

            # Fetch the 'real' pending transaction.
            #
            my $pending = RPS::DB::Item::PendingTransaction->Lookup( pending_transaction_id => $item->pending_transaction_id );

            # !!! Note that amount == amount. no sign flipping.  We have to get the sign right when we create the
            # !!! pending transaction in the first place.
            #
            my $transaction = RPS::DB::Item::FinanceTransaction->Create(
                finance_account_id => $pending->finance_account_id,
                amount             => Common::RSMath::round( $pending->amount, 2 ),
                transaction_date   => $pending->transaction_date,
                type_code          => $pending->type_code,
                memo               => $pending->memo,
                check_number       => $pending->check_number,
                currency_code      => $pending->currency_code,
            );
            $transaction->save();

            # Done with the pending transaction, get rid of it.
            #
            $pending->delete();
        }

        # Now, figure out what sort of adjustment to make on this account based on what happened
        # on this statement.
        #
        my $amount =
          $trackItem->crossed_advance_balance - ( $trackItem->crossed_previous_advance_balance + $trackItem->crossed_transaction_subtotal );

        if ( 0 != $amount ) {
            my $transaction = RPS::DB::Item::FinanceTransaction->Create(
                finance_account_id => $accountID,
                amount             => Common::RSMath::round( $amount, 2 ),
                transaction_date   => Common::DB::Item::kDateTimeNow(),
                type_code          => RPS::DB::Item::FinanceTransaction::kTypeClosingBalance,
                memo               => "mechanical run $gRunLabel, statement $statementID",

                # !!! At the moment we are presuming that _everything_ is done in US Dollars...
                # !!! This will have to change once we start to support different currencies
                # !!! for publishers...
                #
                currency_code => 'USD',
            );
            $transaction->save();

            _report( "   removed transaction amount $amount from license advance account", 2 );
        }
    }
}

sub _applyLicenseAdvance {
    my ( $licenseItem, $statementID ) = @_;

    # Is there any activity?
    #
    if (   0 != $licenseItem->previous_advance_balance
        || 0 != $licenseItem->transaction_subtotal
        || 0 != $licenseItem->advance_balance ) {
        my $statementLicenseID = $licenseItem->mechanical_statement_license_id;
        _report(
            "   mechanical_statement_license $statementLicenseID" . " has an advance balance of " . $licenseItem->previous_advance_balance,
            2
        );

        # Fetch the license, and see if we need to create the advance account.
        #
        my $trackLicense = RPS::DB::Item::TrackLicense->Lookup( track_license_id => $licenseItem->track_license_id );

        # !!! Currently assuming that the license type is NOT controlled comp
        #
        #        assert(RPS::DB::Item::TrackLicense::kPublishingLicense == $trackLicense->type);

        #        my $license = RPS::DB::Item::License->Lookup(license_id => $trackLicense->id);
        my $accountID = $trackLicense->finance_account_id;
        if ( !$accountID ) {

            # Creating the advance account.
            # -- Note that the starting balance is theoretically in two places:
            #    Both in the license table, and in the mechanical_statement_table as
            #    the 'previous_balance' column.
            #    I'm going to use the value in the license table...
            #
            my $licenseID = $trackLicense->track_license_id;
            my $account   = RPS::DB::Item::FinanceAccount->Create(
                description => "advance account for track license $licenseID",
                type_code   => RPS::DB::Item::FinanceAccount::kAccountTypeAdvance,

                # !!! Will need to account for currency_code
                currency_code => 'USD',
            );
            $account->save();

            $accountID = $account->finance_account_id;

            # Update the license with the account id.
            #
            $trackLicense->finance_account_id($accountID);
            $trackLicense->save();
            _report( "      created new advance account $accountID for track license $licenseID", 2 );
        }

        # Now apply all pending transactions, and delete them.
        #
        my $transactionItems = RPS::DB::Item::MechanicalStatementLicenseTransaction->GetByMechanicalStatementLicenseID($statementLicenseID);
        while ( my $item = $transactionItems->next() ) {

            # Fetch the 'real' pending transaction.
            #
            my $pending = RPS::DB::Item::PendingTransaction->Lookup( pending_transaction_id => $item->pending_transaction_id );

            # !!! Note that amount == amount. no sign flipping.  We have to get the sign right when we create the
            # !!! pending transaction in the first place.
            #
            my $transaction = RPS::DB::Item::FinanceTransaction->Create(
                finance_account_id => $pending->finance_account_id,
                amount             => Common::RSMath::round( $pending->amount, 2 ),
                transaction_date   => $pending->transaction_date,
                type_code          => $pending->type_code,
                memo               => $pending->memo,
                check_number       => $pending->check_number,
                currency_code      => $pending->currency_code,
            );
            $transaction->save();

            # Done with the pending transaction, get rid of it.
            #
            $pending->delete();
        }

        # Now, figure out what sort of adjustment to make on this account based on what happened
        # on this statement.
        #
        # !!! Not clear whether this is going to work for 'zeroing-out' the balance if the license was crossed...
        #
        #
        my $amount = $licenseItem->advance_balance - ( $licenseItem->previous_advance_balance + $licenseItem->transaction_subtotal );

        if ( 0 != $amount ) {
            my $transaction = RPS::DB::Item::FinanceTransaction->Create(
                finance_account_id => $accountID,
                amount             => Common::RSMath::round( $amount, 2 ),
                transaction_date   => Common::DB::Item::kDateTimeNow(),
                type_code          => RPS::DB::Item::FinanceTransaction::kTypeClosingBalance,
                memo               => "mechanical run $gRunLabel, statement $statementID",

                # !!! At the moment we are presuming that _everything_ is done in US Dollars...
                # !!! This will have to change once we start to support different currencies
                # !!! for publishers...
                #
                currency_code => 'USD',
            );
            $transaction->save();

            _report( "   removed transaction amount $amount from license advance account", 2 );
        }
    }
}

# !!! This is a misnomer - we don't look at the 'advance' account anymore - no such thing
# !!! There is just 1 account.
#
sub _applyPublisherAdvance {
    my ($statement) = @_;

    my $statementID = $statement->mechanical_statement_id;

    # !!! This is going to have to change.
    #
    # We no longer assume the new balance, or applied to balance.
    #
    # Instead...  we first apply the statement subtotal as a credit,
    # then apply each pending transaction.
    #

    my $publisherID = $statement->publisher_id;
    my $payorID     = $statement->payor_id;

    # Create a new publisher_id/payor_id/account_id mapping if necessary
    #
    my $accountMap = RPS::DB::Item::PublisherAccount->Lookup( publisher_id => $publisherID, payor_id => $payorID );
    if ( !$accountMap ) {
        $accountMap = RPS::DB::Item::PublisherAccount->Create(
            publisher_id => $publisherID,
            payor_id     => $payorID,
        );
        $accountMap->save();
    }

    # Create a new finance account if necessary
    #
    my $accountID = $accountMap->finance_account_id;
    if ( !$accountID ) {

        # Create a new account
        #
        my $account = RPS::DB::Item::FinanceAccount->Create(
            description   => "holdover account for publisher $publisherID payor $payorID",
            type_code     => RPS::DB::Item::FinanceAccount::kAccountTypeHoldover,
            currency_code => 'USD',
        );
        $account->save();
        $accountID = $account->finance_account_id;

        # save the account_id in the map table
        #
        $accountMap->finance_account_id($accountID);
        $accountMap->save();
    }

    # Apply the statement subtotal as a transaction to this account.
    #
    # !!! We want the memo to be the run name - I'd like a consistent interface around that.
    my $statementSubtotal = $statement->subtotal;
    if ( $statementSubtotal < 0 or $statementSubtotal > 0 ) {
        my $transaction = RPS::DB::Item::FinanceTransaction->Create(
            finance_account_id => $accountID,
            amount             => Common::RSMath::round( $statementSubtotal, 2 ),
            transaction_date   => Common::DB::Item::kDateTimeNow(),
            type_code          => RPS::DB::Item::FinanceTransaction::kTypeClosingBalance,
            memo               => "mechanical statement $statementID royalties owed",

            # !!! At the moment we are presuming that _everything_ is done in US Dollars...
            # !!! This will have to change once we start to support different currencies
            # !!! for publishers...
            #
            currency_code => 'USD',
        );
        $transaction->save();
    }

    # Now apply all pending transactions, and delete them.
    #
    my $transactionItems = RPS::DB::Item::MechanicalStatementTransaction->GetByMechanicalStatementID($statementID);
    while ( my $item = $transactionItems->next() ) {

        # Fetch the 'real' pending transaction.
        #
        my $pending = RPS::DB::Item::PendingTransaction->Lookup( pending_transaction_id => $item->pending_transaction_id );

        # !!! Note that amount == amount. no sign flipping.  We have to get the sign right when we create the
        # !!! pending transaction in the first place.
        #
        my $transaction = RPS::DB::Item::FinanceTransaction->Create(
            finance_account_id => $pending->finance_account_id,
            amount             => Common::RSMath::round( $pending->amount, 2 ),
            type_code          => $pending->type_code,
            transaction_date   => $pending->transaction_date,
            memo               => $pending->memo,
            check_number       => $pending->check_number,
            currency_code      => $pending->currency_code,
        );
        $transaction->save();

        # Mark this item as processed.
        #
        #        $pending->processed(1);
        #        $pending->save();

        # No, just delete it.
        #
        $pending->delete();
    }
}

# (XXX)
# !!! This may be a problem.
# !!! I am eliminating all 'unknown publisher' track_licenses.
# !!! So I may need a new way to determine which tracks and albums have
# !!! unallocated shares.
#
# May just have to use a more brute-force method...
#
# Let's just totally re-write this, since the old method relied on the unknown publisher.
#

sub _getUnallocatedTracksAndAlbums {
    my ( $unallocatedTracks, $unallocatedAlbums ) = @_;

    # Scan the track_license table, and note the share for every
    # track and album
    #
    my %trackShares;
    my %albumShares;

    my $trackLicenseList = RPS::DB::Item::TrackLicense->GetAll();
    while ( $trackLicenseList->hasNext() ) {
        my $trackLicense = $trackLicenseList->next();
        my $trackID      = $trackLicense->track_id;

        $trackShares{$trackID} += $trackLicense->share;

        my $track = RPS::DB::Item::Track->Lookup( track_id => $trackID );
        my $albumID = $track->album_id;
        if ($albumID) {
            $albumShares{$albumID} += $trackLicense->share;
        }
    }

    # Now we can figure out which tracks and albums are not fully allocated.
    #
    foreach my $trackID ( keys %trackShares ) {
        if ( $trackShares{$trackID} < 100 ) {
            $unallocatedTracks->{$trackID} = 1;
        }
    }

    # We want to mark _every track_ on these albums as unallocated, even
    # if they technically are - This is the safest way to insure that we
    # know to handle adjustments properly down the road.
    # (Basically, if in the future additional licenses are added to the album,
    #  we will need to possibly make adjustments to the controlled comps on
    #  all that album's tracks.)
    #
    foreach my $albumID ( keys %albumShares ) {
        if ( $albumShares{$albumID} < 100 ) {
            $unallocatedAlbums->{$albumID} = 1;

            my $trackList = RPS::DB::Item::Track->GetTracksByAlbumID($albumID);
            while ( $trackList->hasNext() ) {
                my $anotherTrack = $trackList->next();
                $unallocatedTracks->{ $anotherTrack->track_id } = 1;
            }
        }
    }
}

sub _report {
    my ( $string, $verbosity ) = @_;
    $verbosity = 1 unless defined $verbosity;

    if ( $gVerbosityLevel >= $verbosity ) {
        print STDERR $string . "\n";
    }
}

sub _preflightCheck {
    my ($runID) = @_;

    return 0 unless _pendingTransactionsAreValid($runID);

    # See if all the reserves are present and accounted for...
    #
    if ( RPS::DB::Item::LicenseReserveRun->CountMissingRunReserves($runID) > 0 ) {
        _report("ERROR - Not all run reserves are accounted for");
        return 0;
    }

    return 1;
}

sub _pendingTransactionsAreValid {
    my ($runID) = @_;

    # Make sure nobody deleted the pending transactions that appear on this run!
    #
    my $statements = RPS::DB::Item::MechanicalStatement->GetByMechanicalRunID($runID);
    while ( my $statement = $statements->next() ) {
        my $statementID         = $statement->mechanical_statement_id;
        my $statementTransItems = RPS::DB::Item::MechanicalStatementTransaction->GetByMechanicalStatementID($statementID);
        my $transItem;
        while ( $transItem = $statementTransItems->next() ) {
            my $pendingTransactionID = $transItem->pending_transaction_id;
            my $pending = RPS::DB::Item::PendingTransaction->Lookup( pending_transaction_id => $pendingTransactionID );
            if ( !$pending ) {
                _report( "ERROR - original pending transaction was deleted! : " . Dumper($transItem) );
                return 0;
            }
        }

        my $licenseTransItems = RPS::DB::Item::MechanicalStatementLicenseTransaction->GetByMechanicalStatementID($statementID);
        while ( $transItem = $licenseTransItems->next() ) {
            my $pendingTransactionID = $transItem->pending_transaction_id;
            my $pending = RPS::DB::Item::PendingTransaction->Lookup( pending_transaction_id => $pendingTransactionID );
            if ( !$pending ) {
                _report( "ERROR - original pending transaction was deleted! : " . Dumper($transItem) );
                return 0;
            }
        }
    }

    return 1;
}
