#!/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;
}
