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

use Data::Dumper;
use Getopt::Std;
use Carp;

use Excel::Writer::XLSX;

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


use lib '/app/tools/rps/lib';
use RPS::RoyaltyRun::Status;
use RPS::Statement::Artist::StatementFull;
use RPS::Payor::Payor;
use RPS::ArtistPayee::ArtistPayee;
use RPS::ArtistRoyalty::ArtistRoyaltyRun;
use RPS::DB::Item::ContractRateType;
use RPS::DB::Item::IncomeSource;
use RPS::DB::Item::LicenseIncomeType;
use RPS::DB::Item::Region;
use RPS::DB::Item::Channel;
use RPS::DB::Item::PriceLevel;
use RPS::DB::Item::ArtistRoyaltyStatementSettings;

use lib '/app/tools/rps/bin/statements';
use TableObj;
use Formats;
use EmbedImage;

my $gShowRoyaltyRate = 0;
my $gShowSalesPrice = 0;

my $gShowProration         = 0;
my $gShowProratedRate      = 0;
my $gShowRateReduction     = 0;
my $gShowPackaging         = 0;
my $gShowGrossUnits        = 0;
my $gShowGrossSales        = 0;
my $gShowPercentageOfSales = 0;
my $gShowFreeGoods         = 0;
my $gShowReserves          = 0;
my $gShowLiquidations      = 0;
my $gShowReturns           = 0;

$SIG{__DIE__} = \&Carp::confess;


# Global Excel formatting -- these are used to help 'style' the
# look-and-feel of the various statement cells.
#
my $gFormatHeaderTextBold;

my $gFormatLargeTextBold;
my $gFormatLargeTextBoldUnderline;
my $gFormatLargeTextNormal;
my $gFormatLargeTextNormalUnderline;

my $gFormatMediumTextBold;
my $gFormatMediumTextBoldUnderline;
my $gFormatMediumTextNormal;
my $gFormatMediumTextNormalCurrency;
my $gFormatMediumTextNormalCurrencyUnderline;
my $gFormatMediumTextNormalUnderline;

my $gFormatSmallTextBold;
my $gFormatSmallTextBoldPercent;
my $gFormatSmallTextBoldCurrency;
my $gFormatSmallTextBoldUnderline;
my $gFormatSmallTextBoldUnderlineNoWrap;
my $gFormatSmallTextBoldUnderlineCenter;

my $gFormatSmallTextNormal;
my $gFormatSmallTextNormalCurrency;
my $gFormatSmallTextNormalCurrencyUnderline;
my $gFormatSmallTextNormalInteger;
my $gFormatSmallTextNormalPercent;
my $gFormatSmallTextNormalPercent4;
my $gFormatSmallTextNormalDecimal;
my $gFormatSmallTextNormalUnderline;

my $gFormatSectionHeader;
my $gFormatDetailHeader;
my $gFormatSectionFooterLabel;
my $gFormatAlbumContractName;

my $gFormatLegendHeader;
my $gFormatLegendNameNormal;
my $gFormatLegendNameBottom;
my $gFormatLegendDescNormal;
my $gFormatLegendDescBottom;

use constant kFontName => 'Arial'; # statement font

# This determines how much output we spew forth to STDERR.
# The user can set this with the -V command-line argument.
#
my $gVerbosityLevel = 1;


# Parse the command-line, then create the PDF!
#
my %options;
parseCommandLine(\%options);

createStatementExcel($options{clientID}, $options{artistStatementID}, $options{outputFile});


# ----------------------------------------------------------------------------------------------


# Create a single Excel statement for the specified statementID

sub createStatementExcel
{
    my ($clientID, $artistStatementID, $outFilePath) = @_;

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

    # If an output file path was not specified, create one out of the statement id.
    #
    if( ! $outFilePath )
    {
        $outFilePath = "artist_statement_$artistStatementID.xlsx"; # This is the internal filename
    }

    # Grab the statement settings (some columns are optional), and record these
    # in some _global_ variables.
    #
    my $settings = RPS::DB::Item::ArtistRoyaltyStatementSettings->GetCurrentSettings();
    if ($settings)
    {
        $gShowRoyaltyRate       = $settings->show_royalty_rate; # base rate
        $gShowSalesPrice        = $settings->show_sales_price;
        $gShowProration         = $settings->show_proration;
        $gShowProratedRate      = $settings->show_prorated_rate;
        $gShowRateReduction     = $settings->show_rate_reduction;
        $gShowPackaging         = $settings->show_packaging;
        $gShowGrossUnits        = $settings->show_gross_units;
        $gShowGrossSales        = $settings->show_gross_sales;
        $gShowPercentageOfSales = $settings->show_percent_sales;
        $gShowFreeGoods         = $settings->show_free_goods;
        $gShowReserves          = $settings->show_reserves;
        $gShowLiquidations      = $settings->show_liquidations;
        $gShowReturns           = $settings->show_returns;
    }


    # Instantiate the Excel object.
    #
    my $workbook = Excel::Writer::XLSX->new( $outFilePath );
    my $worksheet = $workbook->add_worksheet("Statement");

    my $legendWorksheet = $workbook->add_worksheet("Legend");

    # Set some column widths; ideally we would base the widths on the size of
    # the data within each column, but we can't autofit a column through the
    # Excel API (this is something you can only do in Excel at run-time).
    #
    # The alternative approach (which we do here) is to just set the width
    # manually on the first eight columns since these are the ones most
    # likely to contain large numbers that could cause ####'s to appear if the
    # number is too large for the default cell width.  The text fields should
    # wrap so we should be OK there.
    #

    $worksheet->set_column( 0, 0, 21 );  # startCol, endCol, width
    $worksheet->set_column( 1, 1, 11 );  # startCol, endCol, width
    $worksheet->set_column( 2, 2, 11 );  # startCol, endCol, width
    $worksheet->set_column( 3, 3, 11 );  # startCol, endCol, width
    $worksheet->set_column( 4, 4, 11 );  # startCol, endCol, width
    $worksheet->set_column( 5, 5, 11 );  # startCol, endCol, width
    $worksheet->set_column( 6, 6, 11 );  # startCol, endCol, width
    $worksheet->set_column( 7, 7, 11 );  # startCol, endCol, width

    $legendWorksheet->set_column( 0, 0, 8 );  # startCol, endCol, width
    $legendWorksheet->set_column( 1, 1, 32 );  # startCol, endCol, width

    # Create formats that we'll use to style the statement
    #
    _initExcelFormats( $workbook );

    # Instantiate the ArtistStatement object.
    # This object will contain all the info we need to output the statement document.
    #
    my $statement = RPS::Statement::Artist::StatementFull->new(artistRoyaltyStatementID => $artistStatementID);

    if( !$statement )
    {
        $workbook->close() or die "Error closing file: $!";
        die("ERROR: Artist Statement ID $artistStatementID is invalid!");
    }

    my $row = 0;
    my $col = 0;

    # Each section of the statement will have its own subroutine.
    #
    ($col, $row ) = header($worksheet, $statement, $col, $row );
    ($col, $row ) = summary($worksheet, $statement, $col, $row );
    ($col, $row ) = details($worksheet, $statement, $col, $row );


    # Let's grab the distinct income source ids so that we can make a legend.
    #
    my $incomeSourceList = $statement->ArtistStatementIncomeSourceListSkipNonPayable()->getList();

    if (defined $incomeSourceList)
    {
        ($col, $row) = addLegend($legendWorksheet, $col, 0, $incomeSourceList);
    }

    # All done, save and clean up
    #
    $workbook->close() or die "Error closing file: $!";

}# createStatementExcel

sub _initExcelFormats {
    my($workbook) = @_;

    # Initialize formats
    #
    $gFormatHeaderTextBold = $workbook->add_format();
    $gFormatHeaderTextBold->set_bold(1);
    $gFormatHeaderTextBold->set_color("black");
    $gFormatHeaderTextBold->set_size(14); # default is 10
    $gFormatHeaderTextBold->set_font( kFontName );

    my $currencySymbol = Common::Client::Current()->Locale()->currencyFormat()->symbol();
    my $_fmt = $currencySymbol . '0.00';

    $gFormatLargeTextBold = $workbook->add_format();
    $gFormatLargeTextBold->set_bold(1);
    $gFormatLargeTextBold->set_color("black");
    $gFormatLargeTextBold->set_size(12);
    $gFormatLargeTextBold->set_font( kFontName );

    $gFormatLargeTextBoldUnderline = $workbook->add_format();
    $gFormatLargeTextBoldUnderline->set_bold(1);
    $gFormatLargeTextBoldUnderline->set_color("black");
    $gFormatLargeTextBoldUnderline->set_size(12);
    $gFormatLargeTextBoldUnderline->set_bottom(1);
    $gFormatLargeTextBoldUnderline->set_font( kFontName );

    $gFormatLargeTextNormal = $workbook->add_format();
    $gFormatLargeTextNormal->set_bold(0);
    $gFormatLargeTextNormal->set_color("black");
    $gFormatLargeTextNormal->set_size(12);
    $gFormatLargeTextNormal->set_font( kFontName );

    $gFormatLargeTextNormalUnderline = $workbook->add_format();
    $gFormatLargeTextNormalUnderline->set_bold(0);
    $gFormatLargeTextNormalUnderline->set_color("black");
    $gFormatLargeTextNormalUnderline->set_size(12);
    $gFormatLargeTextNormalUnderline->set_bottom(1);
    $gFormatLargeTextNormalUnderline->set_font( kFontName );


    # medium text normal
    #
    $gFormatMediumTextNormal = $workbook->add_format();
    $gFormatMediumTextNormal->set_bold(0);
    $gFormatMediumTextNormal->set_color("black");
    $gFormatMediumTextNormal->set_size(10);
    $gFormatMediumTextNormal->set_text_wrap(1);
    $gFormatMediumTextNormal->set_font( kFontName );

    $gFormatMediumTextBold = $workbook->add_format();
    $gFormatMediumTextBold->copy( $gFormatMediumTextNormal );
    $gFormatMediumTextBold->set_bold(1);

    $gFormatMediumTextBoldUnderline = $workbook->add_format();
    $gFormatMediumTextBoldUnderline->copy( $gFormatMediumTextBold );
    $gFormatMediumTextBoldUnderline->set_bottom(1);

    $gFormatMediumTextNormalUnderline = $workbook->add_format();
    $gFormatMediumTextNormalUnderline->copy( $gFormatMediumTextNormal );
    $gFormatMediumTextNormalUnderline->set_bottom(1);

    $gFormatMediumTextNormalCurrency = $workbook->add_format();
    $gFormatMediumTextNormalCurrency->copy( $gFormatMediumTextNormal );
    $gFormatMediumTextNormalCurrency->set_num_format( "[Black]$currencySymbol" . "#,##0.00;[Red]($currencySymbol" ."#,##0.00);". $currencySymbol . "0.00" );

    $gFormatMediumTextNormalCurrencyUnderline = $workbook->add_format();
    $gFormatMediumTextNormalCurrencyUnderline->copy( $gFormatMediumTextNormalUnderline );
    $gFormatMediumTextNormalCurrencyUnderline->set_num_format( "[Black]$currencySymbol" . "#,##0.00;[Red]($currencySymbol" ."#,##0.00);". $currencySymbol . "0.00" );


    # small text normal
    #
    $gFormatSmallTextNormal = $workbook->add_format();
    $gFormatSmallTextNormal->set_bold(0);
    $gFormatSmallTextNormal->set_color("black");
    $gFormatSmallTextNormal->set_size(8);
    $gFormatSmallTextNormal->set_text_wrap(1);
    $gFormatSmallTextNormal->set_align('center');
    $gFormatSmallTextNormal->set_font( kFontName );

    $gFormatSmallTextNormalCurrency = $workbook->add_format();
    $gFormatSmallTextNormalCurrency->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalCurrency->set_num_format( "[Black]$currencySymbol" . "#,##0.00;[Red]($currencySymbol" ."#,##0.00);". $currencySymbol . "0.00" );
    $gFormatSmallTextNormalCurrency->set_align('right');

    $gFormatSmallTextNormalCurrencyUnderline = $workbook->add_format();
    $gFormatSmallTextNormalCurrencyUnderline->copy( $gFormatSmallTextNormalCurrency );
    $gFormatSmallTextNormalCurrencyUnderline->set_bottom(1);

    $gFormatSmallTextNormalInteger = $workbook->add_format();
    $gFormatSmallTextNormalInteger->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalInteger->set_num_format("[Black]#,###;[Red]-#,###;0");

    $gFormatSmallTextNormalPercent = $workbook->add_format();
    $gFormatSmallTextNormalPercent->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalPercent->set_num_format("[Black]0.00%;[Red]-0.00%;0.00%");  # display percent always

    # Percent4 displays four digits of precision
    $gFormatSmallTextNormalPercent4 = $workbook->add_format();
    $gFormatSmallTextNormalPercent4->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalPercent4->set_num_format("[Black]0.0000%;[Red]-0.0000%;0.0000%");  # display percent always

    $gFormatSmallTextNormalDecimal = $workbook->add_format();
    $gFormatSmallTextNormalDecimal->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalDecimal->set_num_format("[Black]0.0000;[Red]-0.0000;0.0000");

    $gFormatSmallTextBold = $workbook->add_format();
    $gFormatSmallTextBold->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextBold->set_bold(1);

    $gFormatSmallTextBoldPercent = $workbook->add_format();
    $gFormatSmallTextBoldPercent->copy( $gFormatSmallTextBold );
    $gFormatSmallTextBoldPercent->set_num_format("[Black]0.00%;[Red]-0.00%;");  # display percent only if positive/negative; blank if zero

    $gFormatSmallTextBoldCurrency = $workbook->add_format();
    $gFormatSmallTextBoldCurrency->copy( $gFormatSmallTextBold );
    $gFormatSmallTextBoldCurrency->set_num_format( "[Black]$currencySymbol" . "#,##0.00;[Red]($currencySymbol" ."#,##0.00);". $currencySymbol . "0.00" );
    $gFormatSmallTextBoldCurrency->set_align('right');

    $gFormatSmallTextBoldUnderline = $workbook->add_format();
    $gFormatSmallTextBoldUnderline->copy( $gFormatSmallTextBold );
    $gFormatSmallTextBoldUnderline->set_bottom(1);

    $gFormatSmallTextBoldUnderlineNoWrap = $workbook->add_format();
    $gFormatSmallTextBoldUnderlineNoWrap->copy( $gFormatSmallTextBold );
    $gFormatSmallTextBoldUnderlineNoWrap->set_bottom(1);
    $gFormatSmallTextBoldUnderlineNoWrap->set_text_wrap(0);

    $gFormatSmallTextNormalUnderline = $workbook->add_format();
    $gFormatSmallTextNormalUnderline->copy( $gFormatSmallTextNormal );
    $gFormatSmallTextNormalUnderline->set_bottom(1);

    $gFormatSmallTextBoldUnderlineCenter = $workbook->add_format();
    $gFormatSmallTextBoldUnderlineCenter->copy( $gFormatSmallTextBoldUnderline );
    $gFormatSmallTextBoldUnderlineCenter->set_align('center');

    # Setting colors -- you can use common color names (see the XLSX API), or
    # for greater range you can specify a color index value.  The latter is tricky
    # because the Microsoft color index chart has index values which are offset by
    # 7 from the color indexes used by Excel::Writer::XLSX. 
    # For example, light gray in the MSFT chart is 15, but with set_color
    # it's 15+7 or 22,  I left the original MSFT index (+ offset) in the color
    # calls below to make it easier to decode the color values.
    #

    # used for the section header above each section
    #
    $gFormatSectionHeader = $workbook->add_format();
    $gFormatSectionHeader->copy( $gFormatSmallTextBoldUnderline );
    $gFormatSectionHeader->set_align('center');
    $gFormatSectionHeader->set_border(1);

    # used for the detail header in each section
    #
    $gFormatDetailHeader = $workbook->add_format();
    $gFormatDetailHeader->copy( $gFormatSmallTextBoldUnderline );
    $gFormatDetailHeader->set_align('center');
    $gFormatDetailHeader->set_border(1);
    $gFormatDetailHeader->set_bg_color( 15+7 ); # light gray

    # used for the section footer below each section
    #
    $gFormatSectionFooterLabel = $workbook->add_format();
    $gFormatSectionFooterLabel->copy( $gFormatSmallTextBold );
    $gFormatSectionFooterLabel->set_align('right');


    # used for the album/contract title above each group of sections
    #
    $gFormatAlbumContractName = $workbook->add_format();
    $gFormatAlbumContractName->copy( $gFormatMediumTextBold );
    $gFormatAlbumContractName->set_text_wrap(0);


    # following are used for formatting the legend
    #
    $gFormatLegendHeader = $workbook->add_format();
    $gFormatLegendHeader->copy( $gFormatMediumTextNormal );
    $gFormatLegendHeader->set_bold(1);
    $gFormatLegendHeader->set_top(1);
    $gFormatLegendHeader->set_left(1);
    $gFormatLegendHeader->set_right(1);
    $gFormatLegendHeader->set_align('center');
    $gFormatLegendHeader->set_bg_color( 15+7 );

    $gFormatLegendNameNormal = $workbook->add_format();
    $gFormatLegendNameNormal->copy( $gFormatMediumTextNormal );
    $gFormatLegendNameNormal->set_bold(1);
    $gFormatLegendNameNormal->set_left(1);

    $gFormatLegendNameBottom = $workbook->add_format();
    $gFormatLegendNameBottom->copy( $gFormatLegendNameNormal );
    $gFormatLegendNameBottom->set_left(1);
    $gFormatLegendNameBottom->set_bottom(1);

    $gFormatLegendDescNormal = $workbook->add_format();
    $gFormatLegendDescNormal->copy( $gFormatMediumTextNormal );
    $gFormatLegendDescNormal->set_right(1);

    $gFormatLegendDescBottom = $workbook->add_format();
    $gFormatLegendDescBottom->copy( $gFormatLegendDescNormal );
    $gFormatLegendDescBottom->set_bottom(1);
}# _initExcelFormats

sub header
{
    my ($worksheet, $statement, $col, $row ) = @_;

    my $statementHeader = 'Artist Royalty Statement';

    # If the state of the run is not 'COMMITTED' or 'CLOSED', then we want to
    # mark the statement so that our users don't mail this statement out.
    #
    my $run = RPS::ArtistRoyalty::ArtistRoyaltyRun->new(artistRoyaltyRunID => $statement->ArtistRoyaltyRunID(), loadSubs => 0);
    if( RPS::RoyaltyRun::Status::kCommitted != $run->Status()
         && RPS::RoyaltyRun::Status::kClosed != $run->Status() )
    {
        $statementHeader .= ' (DRAFT)';
    }


    $worksheet->write( $row++, $col, $statementHeader, $gFormatHeaderTextBold );


    # Output the 'From' data
    #
    my $payor = $statement->Payor();

    my $cityStateLine = $payor->City();
    if ($payor->City() && $payor->StateProvince())
    {
        $cityStateLine .= ","; 
    }
    $cityStateLine .= $payor->StateProvince();
    
    if ($payor->PostalCode())
    {        
        $cityStateLine .=  " " . $payor->PostalCode();   
    } 

    $worksheet->write( $row++, $col, 'From:', $gFormatMediumTextNormal );
    $worksheet->write( $row++, $col, $payor->Name(), $gFormatMediumTextNormal );
    $worksheet->write( $row++, $col, $payor->StreetAddress(), $gFormatMediumTextNormal ) if( $payor->StreetAddress() );;
    $worksheet->write( $row++, $col, $payor->StreetAddress2(), $gFormatMediumTextNormal ) if( $payor->StreetAddress2() );
    $worksheet->write( $row++, $col, $payor->StreetAddress3(), $gFormatMediumTextNormal ) if( $payor->StreetAddress3() );
    $worksheet->write( $row++, $col, $cityStateLine, $gFormatMediumTextNormal ) if( ' ' ne $cityStateLine );
    $worksheet->write( $row++, $col, $payor->CountryCode(), $gFormatMediumTextNormal ) if( $payor->CountryCode() );


    # Add in some space, then print the 'To:' info
    #
    $row++;

    my $payee = $statement->ArtistPayee();

    my $cityStateLine = $payee->City();
    if ($payee->City() && $payee->StateProvince())
    {
        $cityStateLine .= ","; 
    }
    $cityStateLine .= $payee->StateProvince();  
    
    if ($payee->PostalCode())
    {
        $cityStateLine .= " " . $payee->PostalCode(); 
    }     

    $worksheet->write( $row++, $col, $payee->Name(), $gFormatMediumTextNormal );
    $worksheet->write( $row++, $col, 'Client #: ' . $payee->ClientAccountID(), $gFormatMediumTextNormal );
    $worksheet->write( $row++, $col, $payee->StreetAddress(), $gFormatMediumTextNormal ) if( $payee->StreetAddress() );
    $worksheet->write( $row++, $col, $payee->StreetAddress2(), $gFormatMediumTextNormal ) if( $payee->StreetAddress2() );
    $worksheet->write( $row++, $col, $payee->StreetAddress3(), $gFormatMediumTextNormal ) if( $payee->StreetAddress3() );
    $worksheet->write( $row++, $col, $cityStateLine, $gFormatMediumTextNormal ) if( ' ' ne $cityStateLine );
    $worksheet->write( $row++, $col, $payee->CountryCode(), $gFormatMediumTextNormal ) if( $payee->CountryCode() );

    # Now print the run label. Which means we need to fetch the run...
    #
    my $run = RPS::ArtistRoyalty::ArtistRoyaltyRun->new(artistRoyaltyRunID => $statement->ArtistRoyaltyRunID(), loadSubs => 0);

    $row++;
    $worksheet->write( $row++, $col, $run->Label(), $gFormatMediumTextBold ); # run label

    $row++;

    return( $col, $row );
}



sub summary
{
    my ($worksheet, $statement, $col, $row ) = @_;


    # The PDF::Table class doesn't seem to work correctly if there is more than
    # 1 table on a page.  So, going to have to draw this in myself.
    #
    ($col, $row ) = drawBalanceTable($worksheet, $statement, $col, $row );

#    $y -= 18;


    ($col, $row ) = drawProductTotalsTable($worksheet, $statement, $col, $row );

    return ($col, $row);
}

sub drawBalanceTable
{
    my ($worksheet, $statement, $col, $row ) = @_;

    $worksheet->write( $row, $col+1, "Amount",  $gFormatMediumTextBold );
    $worksheet->write( $row, $col+2, "Check #", $gFormatMediumTextBold );
    $worksheet->write( $row, $col+3, "Date",    $gFormatMediumTextBold );
    $worksheet->write( $row, $col+4, "Memo",    $gFormatMediumTextBold );
    $row++;
    $worksheet->write( $row, $col,   "Previous Period Balance", $gFormatMediumTextBold );

    $worksheet->write( $row, $col+1, $statement->PreviousBalance(), $gFormatMediumTextNormalCurrency );
    $row++;

    # Get any transactions
    #
    my $transactionList = $statement->ArtistStatementTransactionList();
    if ($transactionList)
    {
        my $list = $transactionList->getList();
        foreach my $transaction (@$list)
        {
            my $type;
            if ($transaction->TypeCode() == 2)
            {
                $type = 'Adjustment';
            }
            elsif ($transaction->TypeCode() == 3)
            {
                $type = 'Advance';
            }
            elsif ($transaction->TypeCode() == 4)
            {
                $type = 'Payment';
            }

            my $amount = $transaction->Amount();
            $amount = ' ' unless defined $amount;
            my $checkNum = $transaction->CheckNumber();
            $checkNum = ' ' unless defined $checkNum;
            my $date = $transaction->TransactionDate();
            $date = ' ' unless defined $date;
            my $memo = $transaction->Memo();
            $memo = ' ' unless defined $memo;

            # Let's format the date
            if ($date != ' ') 
            {
                $date =  formatDate($date);
            }

            $worksheet->write( $row, $col+1, $amount,   $gFormatMediumTextNormalCurrency );
            $worksheet->write( $row, $col+2, $checkNum, $gFormatMediumTextNormal );
            $worksheet->write( $row, $col+3, $date,     $gFormatMediumTextNormal );
            $worksheet->write( $row, $col+4, $memo,     $gFormatMediumTextNormal );
            $row++;
        }
    }


    $worksheet->write( $row, $col,   "Current Period Royalties:", $gFormatMediumTextBoldUnderline );
    $worksheet->write( $row, $col+1, $statement->Total(), $gFormatMediumTextNormalCurrencyUnderline );
    $row++;


    $worksheet->write( $row, $col,   "Ending Balance:", $gFormatMediumTextBold );
    $worksheet->write( $row, $col+1, $statement->Balance(), $gFormatMediumTextNormalCurrency );
    $row++;

    $row++; # blank line

    $worksheet->write( $row, $col,   "Minimum Payment:", $gFormatMediumTextBold );
    $worksheet->write( $row, $col+1, $statement->MinPayment(), $gFormatMediumTextNormalCurrency );
    $row++;


    # If this publisher is 'on hold', we display 'ON HOLD' rather than the amount due value.
    #
    my $amountDueDisplay;
    my $currencyDisplay;
    if ($statement->OnHold)
    {
        $amountDueDisplay = 'ON-HOLD';
        $currencyDisplay = ' ';
    }
    else
    {
        $amountDueDisplay = formatMoney($statement->AmountDue);
        $currencyDisplay = ' ('.Common::Client::Current()->Locale()->currencyFormat()->currencyCode().')';
    }

    $worksheet->write( $row, $col,   "Amount Payable:", $gFormatMediumTextBold );
    $worksheet->write( $row, $col+1, $amountDueDisplay, $gFormatMediumTextNormalCurrency );
    $worksheet->write( $row, $col+2, $currencyDisplay, $gFormatMediumTextNormal );
    $row++;

    return ($col, $row );
}

sub drawProductTotalsTable
{
    my ($worksheet, $statement, $col, $row ) = @_;


    # Now output the 'product totals' data
    #
    my @productsArray;
    my @rowProps;

    my @crossedArray;
    my @uncrossedArray;

    # Add the header
    #
    push @productsArray, [' ', 'Type', 'Album', 'Sales', 'License Income', 'Expenses', 'Previous Balance', 'Total'];  # the header

    my $headerShown;

    # Grab all the album summary info.
    # We'll sort them into two buckets - crossed and uncrossed products.
    #
    my $albumList = $statement->ArtistStatementAlbumList()->getList();
    foreach my $album (@$albumList)
    {
        # We need to skip the albums whose income items use the non-payable rate type exclusively.
        #
        my $incomeItemList = $album->IncomeItemList()->getList();
        my $skipAlbum = 1;
        foreach my $incomeItem (@$incomeItemList)
        {        
            if ($incomeItem->ContractRateTypeID() != RPS::DB::Item::ContractRateType::kRateTypeNonPayable)
            {
                $skipAlbum = 0;
                last;   
            }
        }
        
        # If there are no sales, then we're dealing with license income
        # and/or expenses, which should be included on the statement.
        #
        if ( @$incomeItemList == 0 ||
             $album->TotalExpenses() != 0 )
        {
           $skipAlbum = 0;
        }

        if ($skipAlbum == 1)
        {
            next;
        }

        my @row;

        push @row, 'Product Sales';
        push @row, $album->Album()->Title();
        push @row, $album->TotalIncome();
        push @row, $album->LicenseIncomeSubtotal();
        push @row, $album->TotalExpenses();
        push @row, $album->PreviousBalance();
        push @row, $album->Total();

        if ($album->IsCrossCollateralized() != 0)
        {
            if (0 == scalar @crossedArray)
            {
                unshift @row, 'Crossed Totals';
            }
            else
            {
                unshift @row, ' ';
            }
            push @crossedArray, \@row;
        }
        else
        {
            if (0 == scalar @uncrossedArray)
            {
                unshift @row, 'Uncrossed Totals';
            }
            else
            {
                unshift @row, ' ';
            }
            push @uncrossedArray, \@row;
        }
    }


    # Now combine the two arrays.
    #
    if (0 != scalar @crossedArray)
    {
        foreach my $crossedRow (@crossedArray)
        {
            push @productsArray, $crossedRow;
            push @rowProps, {};
        }
       
    }
    
    #
    # Now add a row for contract level license income.
    if ($statement->ContractLevelLicenseIncomeSubtotal() != 0 || $statement->ContractLevelLicenseIncomePreviousBalance() != 0)
    {
        my $sectionLabel = 'Crossed Totals';
        if (0 != scalar @crossedArray)
        {
            $sectionLabel = '';
        }

        push @productsArray, 
        [
            $sectionLabel, 
            'Contract License Income', 
            ' ',
            ' ', 
            $statement->ContractLevelLicenseIncomeSubtotal(),
            ' ', 
            $statement->ContractLevelLicenseIncomePreviousBalance(),
            $statement->ContractLevelLicenseIncomeTotal()
        ];

    } 
    
    if ((0 != scalar @crossedArray) || ($statement->ContractLevelLicenseIncomeSubtotal() != 0 || $statement->ContractLevelLicenseIncomePreviousBalance() != 0))   
    { 
        push @productsArray, 
        [
            ' ', 
            'Total:', 
            ' ',
            $statement->CrossCollateralizedIncomeSubTotal(), 
            $statement->LicenseIncomeSubtotal(),
            $statement->CrossCollateralizedExpenseSubTotal(), 
            $statement->CrossCollateralizedPreviousBalance(), 
            $statement->CrossCollateralizedSubTotal()
        ];

    }   

    if (((0 != scalar @crossedArray) || ($statement->ContractLevelLicenseIncomeSubtotal() != 0)) && (0 != scalar @uncrossedArray))
    {
        # Add an empty row.
        # !!! We might be able to do the same thing with a row padding on the next row.
        #
        push @productsArray, [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '];
    }

    if (0 != scalar @uncrossedArray)
    {
        foreach my $uncrossedRow (@uncrossedArray)
        {
            push @productsArray, $uncrossedRow;
        }

        push @productsArray, ['Total:', ' ', ' ', ' ', ' ', ' ', ' ', $statement->Total()];

    }

    # We're only going to create this table if there's someting to display
    #

    if( (0 != scalar @crossedArray) ||
        ($statement->ContractLevelLicenseIncomeSubtotal() != 0) ||
        (0 != scalar @uncrossedArray))
    {

        $row +=2;  # add some blank lines

        # output productArray
        #
        my $idx = 0;
        my $numProducts = scalar @productsArray;

        foreach my $data (@productsArray)
        {
            my $rowLabel      = @$data[0];
            my $type          = @$data[1];
            my $album         = @$data[2];
            my $sales         = @$data[3];
            my $licenseIncome = @$data[4];
            my $expenses      = @$data[5];
            my $previous      = @$data[6];
            my $total         = @$data[7];

            my $rowLabelStyle = $gFormatMediumTextBold; # col 0 label (Crossed Totals, Uncrossed Totals or Total)

            my $lineStyle     = $gFormatMediumTextNormalCurrency;   # product lines

            if( $idx == 0 )
            {
                $lineStyle = $gFormatMediumTextBold; # header
            }
            elsif( $idx < ($numProducts-1) )
            {
                # If next line is total line, then draw line under current row
                #
                if( 'Total:' eq @{$productsArray[$idx+1]}[1] ||  'Total:' eq @{$productsArray[$idx+1]}[0] )
                {
                    $lineStyle     = $gFormatMediumTextNormalCurrencyUnderline;
                    $rowLabelStyle = $gFormatMediumTextBoldUnderline;
                }

            }

            $worksheet->write( $row, $col,   $rowLabel, $rowLabelStyle ) if( ' ' ne $rowLabel );
            $worksheet->write( $row, $col+1, $type, $lineStyle );
            $worksheet->write( $row, $col+2, $album, $lineStyle );
            $worksheet->write( $row, $col+3, $sales, $lineStyle );
            $worksheet->write( $row, $col+4, $licenseIncome, $lineStyle );
            $worksheet->write( $row, $col+5, $expenses, $lineStyle );
            $worksheet->write( $row, $col+6, $previous, $lineStyle );
            $worksheet->write( $row, $col+6, $total, $lineStyle );

            $row++;


            $idx++;
        }

        # ($page, $y) = $tableObj->print($x, $y);

    }


    return( $col, $row );
}


sub details
{
    my ($worksheet, $statement, $col, $row ) = @_;

    my $withLegend = 0;


    # Note: we'll display everything on the current worksheet
    #
    my $albumList = $statement->ArtistStatementAlbumList()->getList();
    foreach my $album (@$albumList)
    {
        
        # We need to skip the albums whose income items use the non-payable rate type exclusively.
        #
        my $incomeItemList = $album->IncomeItemList()->getList();
        my $skipAlbum = 1;
        foreach my $incomeItem (@$incomeItemList)
        {        
            if ($incomeItem->ContractRateTypeID() != RPS::DB::Item::ContractRateType::kRateTypeNonPayable)
            {
                $skipAlbum = 0;
                last;   
            }
        }
        
        # If there are no sales, then we're dealing with license income
        # and/or expenses, which should be included on the statement.
        #
        if ( @$incomeItemList == 0 ||
             $album->TotalExpenses() != 0 )
        {
           $skipAlbum = 0;
        }

        if ($skipAlbum == 1)
        {
            next;
        }        
        
        ($col, $row) = albumDetails($worksheet, $col, $row, $album, $withLegend);
        $withLegend = 0;
    }
    

    ($col, $row) = contractLicenseIncomeDetails($worksheet, $col, $row, $statement, $withLegend);

    return( $col, $row );
}# details


sub _getLastUnitRoyaltiesColumn {
    my $numCols = 8; # if all optional columns are de-selected
    $numCols++ if( $gShowRoyaltyRate       );
    $numCols++ if( $gShowSalesPrice        );
    $numCols++ if( $gShowProration         );
    $numCols++ if( $gShowProratedRate      );
    $numCols++ if( $gShowRateReduction     );
    $numCols++ if( $gShowPackaging         );
    $numCols++ if( $gShowGrossUnits        );
    $numCols++ if( $gShowPercentageOfSales );
    $numCols++ if( $gShowFreeGoods         );
    $numCols++ if( $gShowReserves          );
    $numCols++ if( $gShowLiquidations      );
    $numCols++ if( $gShowReturns           );
    return $numCols;
}

sub _getLastNetRevRoyaltiesColumn {
    my $numCols = 8; # if all optional columns are de-selected
    $numCols++ if( $gShowRoyaltyRate       );
    #$numCols++ if( $gShowSalesPrice        ); # unit-based only
    $numCols++ if( $gShowProration         );
    $numCols++ if( $gShowProratedRate      );
    $numCols++ if( $gShowRateReduction     );
    $numCols++ if( $gShowPackaging         );
    #$numCols++ if( $gShowGrossUnits        ); # unit-based only
    $numCols++ if( $gShowGrossSales        );
    $numCols++ if( $gShowPercentageOfSales );
    $numCols++ if( $gShowFreeGoods         );
    $numCols++ if( $gShowReserves          );
    $numCols++ if( $gShowLiquidations      );
    #$numCols++ if( $gShowReturns           ); # unit-based only
    return $numCols;
}

sub albumDetails
{
    my( $worksheet, $col, $row, $album, $withLegend ) = @_;

    $row++;

    ($col, $row) = albumTitle($worksheet, $col, $row, $album);
    ($col, $row) = unitRoyalties($worksheet, $col, $row, $album);
    ($col, $row) = netRevenueRoyalties($worksheet, $col, $row, $album);
    ($col, $row) = licenseIncomeRoyalties($worksheet, $col, $row, $album);
    ($col, $row) = recoupableExpenses($worksheet, $col, $row, $album);
    ($col, $row) = netExpenses($worksheet, $col, $row, $album);

    $row++;

    $worksheet->write( $row, 0, 'Album Previous Balance:', $gFormatSectionFooterLabel );
    $worksheet->write( $row, 1, $album->PreviousBalance(), $gFormatSmallTextNormalCurrency );
    $row++;

    $worksheet->write( $row, 0, 'Album Total:', $gFormatSectionFooterLabel );
    $worksheet->write( $row, 1, $album->Total(), $gFormatSmallTextBoldCurrency );
    $row++;

    return( $col, $row );
}


sub unitRoyalties
{
    my( $worksheet, $col, $row, $album) = @_;

    my @rows;

    # @columnTemplate is an array of hash references that contains the column
    # title, data source for the column, and a data format type.  Some columns
    # are optional and their inclusion is controlled via the artist statement
    # settings mechanism.
    #
    # Note: this can probably be cleaned-up at some point; but this code is
    # largely based on the PDF statement code so I didn't want to deviate too much.
    #
    my @columnTemplate;
    push @columnTemplate, { 
        title => 'Track Title', 
        func => '_outputTrackTitle', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Source', 
        func => '_outputIncomeSource', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Region', 
        func => '_outputRegion', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Channel', 
        func => '_outputChannel', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Price Tier', 
        func => '_outputPriceTier', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Sales Price', 
        func => '_outputSalesPrice', 
        format => 'money',
    } if $gShowSalesPrice; # Optional column

    push @columnTemplate, { 
        title => 'Rate Type', 
        func => '_outputRateType', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Base Rate', 
        func => '_outputRoyaltyRate', 
        format => 'percent4',
    } if $gShowRoyaltyRate; # Optional column

    push @columnTemplate, { 
        title => 'Proration',  # FB113
        func => '_outputProration', 
        format => 'integer',
    } if $gShowProration; # Optional column

    push @columnTemplate, { 
        title => 'Prorated Rate',  # FB113
        func => '_outputProratedRate', 
        format => 'percent4',
    } if $gShowProratedRate; # Optional column

    push @columnTemplate, { 
        title => 'Rate Reduction',  # FB109
        func => '_outputRateReduction', 
        format => 'percent',
    } if $gShowRateReduction; # Optional column

    push @columnTemplate, { 
        title => 'Packaging',  # FB109
        func => '_outputPackagingDeduction', 
        format => 'percent',
    } if $gShowPackaging; # Optional column

    push @columnTemplate, { 
        title => 'Effective Rate', 
        func => '_outputEffectiveRateUnitRoyalties',
        format => 'decimal',
    };

    push @columnTemplate, { 
        title => 'Gross Units',  # FB109
        func => '_outputGrossUnits', 
        format => 'integer',
    } if $gShowGrossUnits; # Optional column

    push @columnTemplate, { 
        title => '% of Sales',  # FB109
        func => '_outputPercentageOfSales', 
        format => 'percent', 
    } if $gShowPercentageOfSales; # Optional column

    push @columnTemplate, { 
        title => 'Free Goods',  # FB109
        func => '_outputFreeGoodsDeduction', 
        format => 'percent', 
    } if $gShowFreeGoods; # Optional column

    push @columnTemplate, { 
        title => 'Reserves',  # FB109
        func => '_outputUnitsReserved', 
        format => 'integer', 
    } if $gShowReserves; # Optional column

    push @columnTemplate, { 
        title => 'Liquidations',  # FB109
        func => '_outputUnitsLiquidated', 
        format => 'integer', 
    } if $gShowLiquidations; # Optional column

    push @columnTemplate, { 
        title => 'Returns',  # FB109
        func => '_outputReturns', 
        format => 'integer', 
    } if $gShowReturns; # Optional column

    push @columnTemplate, { 
        title => 'Net Units', 
        func => '_outputNetUnits', 
        format => 'integer', 
    };

    push @columnTemplate, { 
        title => 'Total', 
        func => '_outputTotal', 
        format => 'money', 
    };

    my $numColumns = scalar @columnTemplate;

    my @dataFormats;

    # Put the column titles into a row.
    #
    my @tempRow;
    foreach my $colTempRecord (@columnTemplate)
    {
        push @tempRow, $colTempRecord->{title};

        push @dataFormats, $colTempRecord->{format};
    }

    push @rows, \@tempRow;

    # Go through the entire income item list, and pull out those that are not net_revenue rates.
    # Also, skip non-payable items.
    #
    my $weHaveItems = 0;
    my $incomeItemList = $album->IncomeItemList()->getList();
    foreach my $incomeItem (@$incomeItemList)
    {
        next if ($incomeItem->ContractRateTypeID() == RPS::DB::Item::ContractRateType::kRateTypePercentRevenue);
        next if ($incomeItem->ContractRateTypeID() == RPS::DB::Item::ContractRateType::kRateTypeNonPayable);
        $weHaveItems = 1;
        

        my @tempRow;
        my $i=0;
        foreach my $colTempRecord (@columnTemplate)
        {
            # Yes this is wacky.  Invoking a function based on the name.
            #
            no strict 'refs';
            my $funcName = $colTempRecord->{func};
            push @tempRow, &$funcName($incomeItem);
        }
        push @rows, \@tempRow;

    }


    # If there weren't any unit based income items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $worksheet->write( $row, $col, 'Unit Royalties', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;

        # All of the unit royalty data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;
            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalPercent4 if( $_fmt =~ /^percent4$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display 'Net Revenue Total' line
        #
        $worksheet->write( $row, 0, 'Unit Level Income:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->UnitLevelIncome(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# unitRoyalties

sub netRevenueRoyalties
{
    my ($worksheet, $col, $row, $album) = @_;

    my @rows;

    # @columnTemplate is an array of hash references that contains the column
    # title, data source for the column, and a data format type.  Some columns
    # are optional and their inclusion is controlled via the artist statement
    # settings mechanism.
    #
    # Note: this can probably be cleaned-up at some point; but this code is
    # largely based on the PDF statement code so I didn't want to deviate too much.
    #
    my @columnTemplate;
    push @columnTemplate, { 
        title => 'Track Title', 
        func => '_outputTrackTitle', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Source', 
        func => '_outputIncomeSource', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Region', 
        func => '_outputRegion', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Channel', 
        func => '_outputChannel', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Rate Type', 
        func => '_outputRateType', 
        format => 'string',
    };

    push @columnTemplate, { 
        title => 'Base Rate', 
        func => '_outputRoyaltyRate', 
        format => 'percent4',
    } if $gShowRoyaltyRate; # Optional column

    push @columnTemplate, { 
        title => 'Proration', 
        func => '_outputProration', 
        format => 'integer',
    } if $gShowProration; # Optional column

    push @columnTemplate, { 
        title => 'Prorated Rate', 
        func => '_outputProratedRate', 
        format => 'percent4',
    } if $gShowProratedRate; # Optional column

    push @columnTemplate, { 
        title => 'Rate Reduction',  # FB109
        func => '_outputRateReduction', 
        format => 'percent',
    } if $gShowRateReduction; # Optional column

    push @columnTemplate, { 
        title => 'Packaging',  # FB109
        func => '_outputPackagingDeduction', 
        format => 'percent',
    } if $gShowPackaging; # Optional column

    push @columnTemplate, { 
        title => 'Effective Rate', 
        func => '_outputEffectiveRate', 
        format => 'percent4',  # yes, this is different than the unit royalties effective rate
    };

    push @columnTemplate, { 
        title => 'Gross Sales',  # FB703
        func => '_outputGrossSales', 
        format => 'money',
    } if $gShowGrossSales; # Optional column

    push @columnTemplate, { 
        title => '% of Sales',  # FB109
        func => '_outputPercentageOfSales', 
        format => 'percent',
    } if $gShowPercentageOfSales; # Optional column

    push @columnTemplate, { 
        title => 'Free Goods',  # FB109
        format => 'percent',
        func => '_outputFreeGoodsDeduction', 
    } if $gShowFreeGoods; # Optional column

    push @columnTemplate, { 
        title => 'Reserves',  # FB109
        func => '_outputRevenueReserved', 
        format => 'money',
    } if $gShowReserves; # Optional column

    push @columnTemplate, { 
        title => 'Liquidations',  # FB109
        func => '_outputRevenueLiquidated', 
        format => 'money',
    } if $gShowLiquidations; # Optional column

    push @columnTemplate, { 
        title => 'Returns',  # FB109
        func => '_outputReturns', 
        format => 'integer',
    } if $gShowReturns; # Optional column

    push @columnTemplate, { 
        title => 'Net Units', 
        func => '_outputNetUnits', 
        format => 'integer',
    };

    push @columnTemplate, { 
        title => 'Net Sales', 
        func => '_outputNetRevenue', 
        format => 'money',
    };

    push @columnTemplate, { 
        title => 'Total', 
        func => '_outputTotal', 
        format => 'money',
    };

    my $numColumns = scalar @columnTemplate;


    my @dataFormats;


    # Put the column titles into a row.
    #
    my @tempRow;
    my $i = 0;

    foreach my $colTempRecord (@columnTemplate)
    {
        push @tempRow, $colTempRecord->{title};
        push @dataFormats, $colTempRecord->{format};
        $i++;
    }

    push @rows, \@tempRow;

    # Go through the entire income item list, and pull out those that are not net_revenue rates.
    #
    my $weHaveItems = 0;
    my $incomeItemList = $album->IncomeItemList()->getList();
    foreach my $incomeItem (@$incomeItemList)
    {
        next if ($incomeItem->ContractRateTypeID() != RPS::DB::Item::ContractRateType::kRateTypePercentRevenue);
        $weHaveItems = 1;
        

        my @tempRow;
        foreach my $colTempRecord (@columnTemplate)
        {
            # Yes this is wacky.  Invoking a function based on the name.
            #
            no strict 'refs';
            my $funcName = $colTempRecord->{func};
            push @tempRow, &$funcName($incomeItem);
        }
        push @rows, \@tempRow;

    }


    # If there weren't any unit based income items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $row++;
        $worksheet->write( $row, $col, 'Net Revenue Royalties', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;


        # All of the net revenue royalty data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;

            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalPercent4 if( $_fmt =~ /^percent4$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display 'Net Revenue Total' line
        #
        $worksheet->write( $row, 0, 'Net Revenue Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->NetRevenueIncome(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# netRevenueRoyalties


sub licenseIncomeRoyalties
{
    my ($worksheet, $col, $row, $album) = @_;

    my @rows;

    # Set up the header
    #
    push @rows,        ['Track Title', 'Income Type', 'Memo',   'Rate Type', 'Rate',   'Units',  'Sales', 'Total'];
    my @dataFormats =  ('string',      'string',      'string', 'string',   'percent', 'integer', 'money', 'money');

    my $numColumns = 8;

    # Go through the entire license income item list.
    #
    my $weHaveItems = 0;
    my $incomeItemList = $album->LicenseIncomeItemList()->getList();
    foreach my $incomeItem (@$incomeItemList)
    {
        $weHaveItems = 1;
        
        push @rows,
        [
            $incomeItem->TrackName(),
            licenseIncomeTypeIDToName($incomeItem->LicenseIncomeTypeID()),
            $incomeItem->Memo(),
            "% Net Revenue",
            $incomeItem->Rate() / 100,
            $incomeItem->Units(),
            $incomeItem->Revenue(),
            $incomeItem->NetRevenue(),
        ];

    }


    # If there weren't any license income items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $row++;
        $worksheet->write( $row, $col, 'License Income Royalties', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;


        # All of the license income data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;

            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display 'License Income Total' line
        #
        $worksheet->write( $row, 0, 'License Income Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->LicenseIncomeSubtotal(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# licenseIncomeRoyalties


sub albumTitle
{
    my ($worksheet, $col, $row, $album) = @_;

    my $albumTitle = $album->Album()->Title();
    my $catalogNumber = $album->Album()->CatalogNumber();
    my $contractName = $album->ContractName();

    # The title is the albumName, followed by the catalogNumber in parentheses,
    # followed by the contractName.
    #
    my $statementTitle = "$albumTitle ($catalogNumber) - $contractName";

    $worksheet->write( $row, $col, $statementTitle, $gFormatAlbumContractName );

    # Draw empty cells
    #
    my $maxcols = _getLastUnitRoyaltiesColumn();
    for( my $i=1; $i<($maxcols+1); $i++ )
    {
        $worksheet->write( $row, $col+$i, '', $gFormatAlbumContractName );
    }

    $row++;

    return( $col, $row );
}


sub recoupableExpenses
{
    my ($worksheet, $col, $row, $album) = @_;

    my @rows;

    # Set up the header
    #
    push @rows,        ['Description', 'Memo',  'Cost',     '',     'Rate',  'Total'];
    my @dataFormats =  ('string',     'string', 'money', 'string', 'percent', 'money');
    my $numColumns = 6;


    # Pull out the recoupable expenses, which have the 'UsesDefaultNetRate' flag set to 0
    #
    my $weHaveItems = 0;
    my $expenseItemList = $album->ExpenseItemList()->getList();
    foreach my $expenseItem (@$expenseItemList)
    {
        next if (1 == $expenseItem->UsesDefaultNetRate());
        $weHaveItems = 1;
        

        push @rows,
        [
            $expenseItem->ExpenseName(),
            $expenseItem->Memo(),
            $expenseItem->Cost(),
            '',
            $expenseItem->Rate(),
            $expenseItem->Total(),
        ];

    }

    # If there weren't any expense items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $row++;
        $worksheet->write( $row, $col, 'Recoupable Expenses', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;


        # All of the recoupable expense data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;

            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display 'Recoupable Expenses Total' line
        #
        $worksheet->write( $row, 0, 'Recoupable Expenses Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->RecoupableExpenses(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# recoupableExpenses

sub _mysort {
   my $aName = $a->ExpenseName();
   my $bName = $b->ExpenseName();
   if( $aName =~ /^\d+[\.\- ]/ && $bName =~ /^\d+[\.\- ]/ )
   {
       $aName =~ s/^(\d+)[\.\- ].+/$1/;
       $bName =~ s/^(\d+)[\.\- ].+/$1/;
       return $aName <=> $bName;
   }
   return $aName cmp $bName;
}

sub netExpenses
{
    my ($worksheet, $col, $row, $album) = @_;

    my @rows;
    my @rowProps;

    # Set up the header
    #
    push @rows,      ['Description', 'Memo',   'Cost',   ' ',     'Rate',   'Total'];
    my @dataFormats = ('string',    'string', 'money', 'string', 'percent', 'money');
    my $numColumns = 6;



    # Pull out the net revenue expenses, which have the 'UsesDefaultNetRate' flag set to 1
    #
    my $weHaveItems = 0;
    my $expenseItemList = $album->ExpenseItemList()->getList();
    foreach my $expenseItem (sort _mysort @$expenseItemList)
    {
        next if (1 != $expenseItem->UsesDefaultNetRate());
        $weHaveItems = 1;
        

        push @rows,
        [
            $expenseItem->ExpenseName(),
            $expenseItem->Memo(),
            $expenseItem->Cost(),
            '',
            $expenseItem->Rate(),
            $expenseItem->Total(),
        ];

        push @rowProps, {};
    }

    # If there weren't any expense items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $row++;
        $worksheet->write( $row, $col, 'Net Expenses', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;


        # All of the net expense data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;

            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display net expenses footer
        #
        $worksheet->write( $row, 0, 'Net Expenses Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->NetRevenueExpenses(), $gFormatSmallTextBoldCurrency );
        $row++;
        $worksheet->write( $row, 0, 'Net Rate:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->DefaultNetRevenueRate(), $gFormatSmallTextBoldPercent );
        $row++;
        $worksheet->write( $row, 0, 'Net Expenses Adjusted Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $album->NetRevenueExpensesSubTotal(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# netExpenses


sub contractLicenseIncomeDetails
{
    my ($worksheet, $col, $row, $statement, $withLegend) = @_;
    
    my @rows;

    # Set up the header
    #
    push @rows, ['Contract Title', 'Income Type', 'Memo', 'Rate Type', 'Rate', 'Units', 'Sales',    'Total'];
    my @dataFormats = ('string',    'string',     'string', 'string',  'percent', 'integer', 'money', 'money');
    my $numColumns = 8;

    # Go through the entire license income item list.
    #
    my $weHaveItems = 0;
    my $incomeItemList = $statement->ArtistStatementLicenseIncomeItemList()->getList();
    foreach my $incomeItem (@$incomeItemList)
    {
        $weHaveItems = 1;
        

        # !!! We may have to truncate titles?
        # !!! Either that, or we implement some sort of cell wrapping.
        #
        push @rows,
        [
            $incomeItem->ContractTitle(),
            licenseIncomeTypeIDToName($incomeItem->LicenseIncomeTypeID()),
            $incomeItem->Memo(),
            "% Net Revenue",
            $incomeItem->Rate(),
            $incomeItem->Units(),
            $incomeItem->Revenue(),
            $incomeItem->NetRevenue(),
        ];

    }


    # If there weren't any contract license income items, well, we don't even output this section.
    #
    if ($weHaveItems)
    {
        # Section header
        #
        $row++;
        $worksheet->write( $row, $col, 'Contract Level License Income Royalties', $gFormatSectionHeader );

        # Underline the rest of the row
        #
        for( my $i=1; $i<$numColumns; $i++)
        {
            $worksheet->write( $row, $col+$i, ' ', $gFormatSmallTextBoldUnderline );
        }
        $row++;


        # All of the contract license income data is in '@rows'.  Just loop over
        # everything and output cells as we go.
        #
        my $_rowIndex = 0;
        foreach my $data (@rows)
        {
            my $i= 0;

            foreach my $cdata (@$data)
            {
                my $_fmt = $dataFormats[$i];
                my $format = $gFormatSmallTextNormal;

                if( $_rowIndex == 0 )
                {
                    $format = $gFormatDetailHeader;
                }
                else
                {
                    $format = $gFormatSmallTextNormalInteger if( $_fmt =~ /^integer$/i );
                    $format = $gFormatSmallTextNormalPercent if( $_fmt =~ /^percent$/i );
                    $format = $gFormatSmallTextNormalDecimal if( $_fmt =~ /^decimal$/i );

                    # if last line, place underline under 'total' column
                    #
                    if( $_fmt =~ /^money$/i )
                    {
                        if( $_rowIndex == (scalar @rows - 1) && $i == (scalar @$data - 1 ) )
                        {
                            $format = $gFormatSmallTextNormalCurrencyUnderline;
                        }
                        else
                        {
                            $format = $gFormatSmallTextNormalCurrency;
                        }
                    }
                }

                $worksheet->write( $row, $col+$i, $cdata, $format );


                $i++;
            }
            $row++; # Next Excel row

            $_rowIndex++;
        }

        # Display 'License Income Total' line
        #
        $worksheet->write( $row, 0, 'License Income Total:', $gFormatSectionFooterLabel );
        $worksheet->write( $row, $numColumns - 1, $statement->ContractLevelLicenseIncomeSubtotal(), $gFormatSmallTextBoldCurrency );
        $row++;
    }

    return ($col, $row);
}# contractLicenseIncomeDetails

sub addLegend
{
    my( $worksheet, $col, $row, $incomeSourceList) = @_;

    # Add source legend to the first page only.
    #
    
    # Create a hash of income source names and descriptions.
    # There's probably somewhere we can fetch these from,
    # but since some of them have to be hard-coded anyway,
    # we're just going to do that for all of them for now.
    my %incomeSourceDescriptions = (
        'BG', 'Background',
        'CAS', 'Cassette',
        'CAS5', 'Cassette Single',
        'CD', 'Compact Disc',
        'CD2', '2-Disc CD',
        'CD5', 'CD Single',
        'DA', 'Digital Album Permanent Download',
        'DA-P', 'Premium Digital Album Download',
        'DA-U', 'Digital Album Download Upgrade',
        'DD', 'Dual Download',
        'DS', 'Digital Stream',
        'DT', 'Digital Track Permanent Download',
        'DT-P', 'Premium Digital Track Download',
        'DT-U', 'Digital Track Download Upgrade',
        'DTETH', 'Digital Tethered Download',
        'DVDCD', 'DVD CD Set',
        'BD', 'BluRay DVD',
        'JB', 'Jukebox',
        'LP', '12 Inch Vinyl',
        'LP5', 'Vinyl Single',
        'MASTER', 'Master Use License',
        'PI', 'Performance Income',
        'RING', 'Ringtone',
        'SYNC', 'Synchronization Use License',
        'VHS', 'VHS Video',
        'VPD', 'Variable Priced Download'
    );
        
    
    my @incomeSourceNames;
    my $i = 0;
    foreach my $incomeSourceID (@$incomeSourceList) 
    {
        $incomeSourceNames[$i] = incomeSourceIDToName($incomeSourceID),
        $i++;
    }    
    @incomeSourceNames = sort(@incomeSourceNames);    
        

    #$worksheet->write( $row++, 0, 'Source Abbreviations', $gFormatLegendHeader );
    $worksheet->merge_range('A1:B1', 'Source Abbreviations', $gFormatLegendHeader );
    $row++;
    
    my $i=0;
    foreach my $incomeSourceName (@incomeSourceNames) 
    {

        my $_nameFormat = ( $i == (scalar @incomeSourceNames -1) ) ?  $gFormatLegendNameBottom : $gFormatLegendNameNormal;
        my $_descFormat = ( $i == (scalar @incomeSourceNames -1) ) ?  $gFormatLegendDescBottom : $gFormatLegendDescNormal;

        $worksheet->write( $row, 0, $incomeSourceName, $_nameFormat );
        $worksheet->write( $row, 1, $incomeSourceDescriptions{$incomeSourceName}, $_descFormat );
        $row++;

        $i++;
    }
    
    return ($col, $row);
}


sub formatNumber
{
    my ($value) = @_;

#    $value = Formats::formatNumber($value);

    return $value;
}


sub formatMoney
{
    my ($value) = @_;

    #$value = Formats::formatMoney($value);

    return $value;
}


sub formatPercent
{
    my ($value) = @_;

#    $value = Formats::formatPercent($value);

    return $value;
}

sub formatDate
{
    my ($value) = @_;

    $value = Formats::formatDate($value);

    return $value;
}


# These functions map ids to their text representations.
# Basically, the first time we ask for a particular id->string mapping, we
# will query the correct database, and build a hash.  Subsequent calls just
# hit the hash.
#
# Because these tables are so similar, I abstracted the guts into the _genericMapAccessor
# method (to save myself some typing).
#
my %idMaps;

sub _genericMapAccessor
{
    my ($collectionAccessor, $idName, $id) = @_;

    if (! defined $idMaps{$collectionAccessor})
    {
        $idMaps{$collectionAccessor} = {};


        # This is a very naughty thing to do, but it works great in this context.
        # 
#        no strict 'refs';
#        my $c = &$collectionAccessor();
#        use strict 'refs';
        my $c = $collectionAccessor->GetAll();


        while (my $item = $c->next())
        {
            $idMaps{$collectionAccessor}{$item->$idName()} = $item->name;
        }
    }

    return $idMaps{$collectionAccessor}{$id};
}

sub incomeSourceIDToName
{
    my ($id) = @_;
    return _genericMapAccessor('RPS::DB::Item::IncomeSource', 'income_source_id', $id);
}

sub licenseIncomeTypeIDToName
{
    my ($id) = @_;
    return _genericMapAccessor('RPS::DB::Item::LicenseIncomeType', 'license_income_type_id', $id);
}

sub regionIDToName
{
    my ($id, $defaultRate) = @_;
    
    if ($id == 0 && $defaultRate == 1) 
    {
        return 'All';
    }
    else
    {
        return _genericMapAccessor('RPS::DB::Item::Region', 'region_id', $id);
    }
}

sub channelIDToName
{
    my ($id) = @_;
    return _genericMapAccessor('RPS::DB::Item::Channel', 'channel_id', $id);
}

sub priceLevelIDToName
{
    my ($id) = @_;
    return _genericMapAccessor('RPS::DB::Item::PriceLevel', 'price_level_id', $id);
}

sub contractRateTypeIDToName
{
    my ($id) = @_;
    return _genericMapAccessor('RPS::DB::Item::ContractRateType', 'contract_rate_type_id', $id);
}



#
# Boring script stuff below...
#

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

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

    if (! $opt{s} || ! $opt{c})
    {
        usage();
        exit(1);
    }
    $settings->{artistStatementID} = $opt{s};
    $settings->{clientID}          = $opt{c};
    $settings->{outputFile}        = $opt{f};

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


sub usage
{
    print STDERR "\nusage: $0 -c <client_id> -s <artist_statement_id> [-f <output file>]\n";
    print STDERR "\n";
    print STDERR "Arguments:\n";
    print STDERR "\t-c <client_id>\t\t\tThe client_id of the client to process\n";
    print STDERR "\t-s <artist_statement_id>\tThe id of the artist royalty statement to convert to Excel\n";
    print STDERR "\t-f <output file>\tThe output file. Optional. If not provided, we'll use current directory.\n";
}


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

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

sub _outputTrackTitle
{
    my ($incomeItem) = @_;
    return $incomeItem->TrackName();
}

sub _outputIncomeSource
{
    my ($incomeItem) = @_;
    return incomeSourceIDToName($incomeItem->IncomeSourceID());
}

sub _outputRegion
{
    my ($incomeItem) = @_;
    return regionIDToName($incomeItem->RegionID(),$incomeItem->UsesDefaultNetRate());
}


sub _outputChannel
{
    my ($incomeItem) = @_;

    return channelIDToName($incomeItem->ChannelID());
}


sub _outputPriceTier
{
    my ($incomeItem) = @_;

    return priceLevelIDToName($incomeItem->PriceLevelID());
}


sub _outputSalesPrice
{
    my ($incomeItem) = @_;

    # Price is always '1' (and irrelevant) for fixed rate items.
    #
    return ' ' if $incomeItem->ContractRateTypeID() == RPS::DB::Item::ContractRateType::kRateTypeFixed;

    return formatMoney($incomeItem->Price());
}


sub _outputRateType
{
    my ($incomeItem) = @_;

    return contractRateTypeIDToName($incomeItem->ContractRateTypeID());
}

sub _outputRoyaltyRate
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->BaseRate() / 100); # FB113
}

sub _outputProration # FB113
{
    my ($incomeItem) = @_;

    if( $incomeItem->TrackName() && '' ne $incomeItem->TrackName )
    {
        if( $incomeItem->ProrateTrackCount() != 0 )
        {
            return formatNumber($incomeItem->ProrateTrackCount());
        }
        else
        {
            #return formatNumber(1);
            return ' ';
        }
    }

    return ' ';
}

sub _outputProratedRate # FB113
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->Rate() / 100 )
       if( $incomeItem->TrackName() && '' ne $incomeItem->TrackName );

    return ' ';
}

sub _outputRateReduction # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->RateReduction() / 100);
}

sub _outputPackagingDeduction # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->PackagingDeduction() / 100);
}

sub _outputEffectiveRate
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->NetRate() / 100);
}

sub _outputEffectiveRateUnitRoyalties
{
    my ($incomeItem) = @_;

    # Same as _outputEffectiveRate, but treat as actual rate vs. a percentage
    return formatNumber($incomeItem->NetRate());
}


sub _outputGrossUnits # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->Sales());
}

sub _outputGrossSales # FB703
{
    my ($incomeItem) = @_;

    return formatMoney($incomeItem->Revenue());
}

sub _outputPercentageOfSales # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->PercentageOfSales() / 100);
}

sub _outputFreeGoodsDeduction # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->FreeGoodsDeduction() / 100);
}

sub _outputUnitsReserved # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->UnitsReserved());
}

sub _outputRevenueReserved # FB109
{
    my ($incomeItem) = @_;

    return formatMoney($incomeItem->RevenueReserved());
}

sub _outputUnitsLiquidated # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->UnitsLiquidated());
}

sub _outputRevenueLiquidated # FB109
{
    my ($incomeItem) = @_;

    return formatMoney($incomeItem->RevenueLiquidated());
}

sub _outputReturns # FB109
{
    my ($incomeItem) = @_;

    return formatNumber($incomeItem->Returns());
}

sub _outputNetUnits
{
    my ($incomeItem) = @_;
    return formatNumber($incomeItem->NetUnits());
}


sub _outputNetRevenue
{
    my ($incomeItem) = @_;
    return formatMoney($incomeItem->NetRevenue());
}

sub _outputTotal
{
    my ($incomeItem) = @_;

    return formatMoney($incomeItem->Total());
}



