#! /usr/bin/perl
# This script will monitor the user specified Asset accounts and notify the
# specified users via email when certain criteria are met.

# xiwa_monitor_accounts - Created by James A. Pattie, (james@pcxperience.com)
# Copyright (c) 2002, Xperience, Inc. (http://www.pcxperience.com/)
# 10/16/2002

use DBIWrapper;
use Portal::Language;
use Portal::Data::Config;
use Portal::Log;
use Portal::Accounting::DB::Main;
use Portal::Application;
use Portal::Auth;
use Mail::Sender;
use Portal::Methods;
use strict;
my $debug = 0;

my $hostName = `/bin/hostname`; chomp $hostName;
my $fromUser = "XIWA <xiwa\@$hostName>";  # you need to change this to the email account you want to use!

my $langObj = Portal::Language->new(lang => 'en');
if ($langObj->error)
{
  die "Error instantiating Portal::Language->new()!  Error = " . $langObj->errorMessage() . "\n";
}

my $configObj = undef;
eval { $configObj = Portal::Data::Config->new(langObj => $langObj); };
if ($@)
{
  die "Instantiating Portal::Data::Config failed!\n$@";
}

my $methods = Portal::Methods->new(langObj => $langObj);
if ($methods->error)
{
  myDie(error => $methods->errorMessage, configObj => $configObj);
}

# make connection to the portal database
my $portalDB = $methods->portalDBSetup(type => "portal", configObj => $configObj);
if ($methods->error)
{
  myDie(error => $methods->errorMessage . "\n", configObj => $configObj);
}
if ($portalDB->error)
{
  myDie(error => $portalDB->errorMessage, configObj => $configObj);
}

# make an instance of the Application Object.
my $appObj = Portal::Application->new(portalDB => $portalDB, langObj => $langObj);
if ($appObj->error)
{
  myDie(error => $appObj->errorMessage, configObj => $configObj);
}

# make an instance of the Auth Object.
my $authObj = Portal::Auth->new(portalDB => $portalDB, langObj => $langObj);
if ($authObj->error)
{
  myDie(error => $authObj->errorMessage, configObj => $configObj);
}

# now get the information about ourselves in the portal. (Our app id, etc.)
my $appInfoObj = $appObj->getApplicationInfo(name => "Accounting");
if (!defined $appInfoObj)
{
  if ($appObj->error)
  {
    myDie(error => $appObj->errorMessage, configObj => $configObj);
  }
  else
  {
    myDie(error => "Application 'Accounting' does not exist!\n", configObj => $configObj);
  }
}
else
{
  if ($appObj->error)
  {
    myDie(error => $appObj->errorMessage, configObj => $configObj);
  }
}

# now get a list of all CompanyApplicationObjects that are for this app/server combo.
my @apps = $appObj->getCompanyAppsForServer(serverName => $configObj->{myHostName}, appObj => $appInfoObj);
if ($appObj->error)
{
  myDie(error => $appObj->errorMessage, configObj => $configObj);
}

if (scalar @apps == 0)
{
  myDie(error => "There are no companies configured to use the 'Accounting' app on server '$configObj->{myHostName}'!\n", configObj => $configObj);
}
# work with the entries returned
foreach my $companyAppObj (@apps)
{
  if ($debug)
  {
    print "Company with id = '$companyAppObj->{companyId}', Database Name = '$companyAppObj->{dbName}'\n";
  }

  # now make a connection to the database.
  my $accountDB = DBIWrapper->new(dbType => $companyAppObj->dbType, dbHost => $companyAppObj->dbHost, dbName => $companyAppObj->dbName, dbUser => $configObj->dbUser, dbPasswd => $configObj->dbPasswd, dbPort => $companyAppObj->dbPort);
  if ($accountDB->error)
  {
    myDie(error => $accountDB->errorMessage, configObj => $configObj);
  }

  # instantiate an instance of Accounting::DB::Main to do the actual dirty work for us.
  my $dbMain = Portal::Accounting::DB::Main->new(dbHandle => $accountDB, langObj => $langObj);
  if ($dbMain->error)
  {
    myDie(error => $dbMain->errorMessage, configObj => $configObj);
  }

  # instantiate an instance of Accounting::DB::Ledger to work with ledgers.
  my $ledgerObj = Portal::Accounting::Objects::Ledger->new(dbHandle => $accountDB, langObj => $langObj);
  if ($ledgerObj->error)
  {
    myDie(error => $ledgerObj->errorMessage, configObj => $configObj);
  }

  # validate we are at least a 1.5+ database.
  my $dbVersion = $dbMain->getDBVersion;
  if ($dbMain->error)
  {
    myDie(error => $dbMain->errorMessage, configObj => $configObj);
  }
  if ($dbVersion < 1.5)
  {
    myDie(error => "The database is at version '$dbVersion' which is too old to support the Monitor Accounts feature!\nIt needs to be at least 1.5!\n\n", configObj => $configObj);
  }

  # lookup the monitorAccountsMailServer entry.
  my $mailServer = $dbMain->getConfigEntry(name => "monitorAccountsMailServer");
  if ($dbMain->error)
  {
    myDie(error => $dbMain->errorMessage, configObj => $configObj);
  }
  if (!defined $mailServer)
  {
    myDie(error => "monitorAccountsMailServer undefined!\n", configObj => $configObj);
  }

  # now get the list of Accounts to Monitor
  my @monitorAccounts = $dbMain->getMonitorEntries;
  if ($dbMain->error)
  {
    myDie(error => $dbMain->errorMessage, configObj => $configObj);
  }
  foreach my $monitorAccount (@monitorAccounts)
  {
    if ($debug)
    {
      print $monitorAccount->export . "\n";
    }
    my $mailNeeded = 0;  # by default we do not need to send an email.
    my $ctype = "text/plain";

    # setup the email to send if we find something the user wanted to be informed of.
    my $mailBody = "";
    if ($monitorAccount->{mailType} eq "text")
    {
      $mailBody .= "The following problems were found for the accounts you are monitoring.\n\n";
      $mailBody .= "Ledger\t| Account\t| <\t| Op  | >\t| Value\n";
      $mailBody .= "-" x 8 . "+" . "-" x 15 . "+" . "-" x 7 . "+" . "-" x 5 . "+" . "-" x 9 . "+" . "-" x 6 . "\n";
    }
    elsif ($monitorAccount->{mailType} eq "html")
    {
      $ctype = "text/html";
      $mailBody .= "<html>\n  <head><title>Monitor Accounts Report</title></head>\n  <body bgcolor=\"white\">\n";
      $mailBody .= "    <table border=\"1\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n";
      $mailBody .= "      <tr>\n";
      $mailBody .= "        <th>Ledger</th><th>Account</th><th>&lt;</th><th>Operator</th><th>&gt;</th><th>Value</th>\n";
      $mailBody .= "      </tr>\n";
    }

    # lookup the ledger name from the ledgerCode.
    $ledgerObj->set('code',$monitorAccount->{ledgerCode});
    $ledgerObj->select;
    if ($ledgerObj->error)
    {
      myDie(error => $ledgerObj->errorMessage, configObj => $configObj);
    }
    my $ledgerName = $ledgerObj->get('name');

    # now we need to get the balances for the specified accounts and do the comparisons.
    my @accounts = split /,/, $monitorAccount->{accounts};
    foreach my $account (@accounts)
    {
      # lookup the account name
      my $accountObj = $dbMain->getAccountInfo(number => $account);
      if ($dbMain->error)
      {
        myDie(error => $dbMain->errorMessage, configObj => $configObj);
      }
      my $accountName = $accountObj->name;

      my $balance = $dbMain->getAccountBalance(account => $account, date => "now()", ledgerCode => $monitorAccount->{ledgerCode});
      if ($dbMain->error)
      {
        myDie(error => $dbMain->errorMessage, configObj => $configObj);
      }
      print "account = '$account - $accountName', balance = '$balance'\n" if $debug;

      # now do the comparisons.

      # see if we have to compare both lessThan and greaterThan values
      if ($monitorAccount->{lessThan} && $monitorAccount->{greaterThan})
      {
        print "< $monitorAccount->{lessThan} $monitorAccount->{operator} > $monitorAccount->{greaterThan}\n" if $debug;
        my $invalid;
        eval "\$invalid = (\$balance < \$monitorAccount->{lessThan} $monitorAccount->{operator} \$balance > \$monitorAccount->{greaterThan});";
        if ($invalid)
        {
          print "invalid!\n" if $debug;
          $mailNeeded = 1;
          if ($monitorAccount->{mailType} eq "text")
          {
            $mailBody .= "$ledgerName\t| $account - $accountName\t| $monitorAccount->{lessThan}\t| $monitorAccount->{operator}";
            $mailBody .= ($monitorAccount->{operator} eq "or" ? " " : "");  # add a white space if or (formatting)
            $mailBody .= " | $monitorAccount->{greaterThan}\t| $balance\n";
          }
          elsif ($monitorAccount->{mailType} eq "html")
          {
            $mailBody .= "      <tr>\n";
            $mailBody .= "        <td>$ledgerName</td><td>$account - $accountName</td><td align=\"right\">$monitorAccount->{lessThan}</td><td align=\"center\">$monitorAccount->{operator}</td><td align=\"right\">$monitorAccount->{greaterThan}</td><td align=\"right\">$balance</td>\n";
            $mailBody .= "      </tr>\n";
          }
        }
      }
      elsif ($monitorAccount->{lessThan})
      {
        print "< $monitorAccount->{lessThan}\n" if $debug;
        if ($balance < $monitorAccount->{lessThan})
        {
          print "invalid!\n" if $debug;
          $mailNeeded = 1;
          if ($monitorAccount->{mailType} eq "text")
          {
            $mailBody .= "$ledgerName\t| $account - $accountName\t| $monitorAccount->{lessThan}\t| $monitorAccount->{operator}";
            $mailBody .= ($monitorAccount->{operator} eq "or" ? " " : "");  # add a white space if or (formatting)
            $mailBody .= " | \t| $balance\n";
          }
          elsif ($monitorAccount->{mailType} eq "html")
          {
            $mailBody .= "      <tr>\n";
            $mailBody .= "        <td>$ledgerName</td><td>$account - $accountName</td><td align=\"right\">$monitorAccount->{lessThan}</td><td align=\"center\">$monitorAccount->{operator}</td><td>&nbsp;</td><td align=\"right\">$balance</td>\n";
            $mailBody .= "      </tr>\n";
          }
        }
      }
      elsif ($monitorAccount->{greaterThan})
      {
        print "> $monitorAccount->{greaterThan}\n" if $debug;
        if ($balance > $monitorAccount->{greaterThan})
        {
          print "invalid!\n" if $debug;
          $mailNeeded = 1;
          if ($monitorAccount->{mailType} eq "text")
          {
            $mailBody .= "$ledgerName\t| $account - $accountName\t| \t| $monitorAccount->{operator}";
            $mailBody .= ($monitorAccount->{operator} eq "or" ? " " : "");  # add a white space if or (formatting)
            $mailBody .=" | $monitorAccount->{greaterThan}\t| $balance\n";
          }
          elsif ($monitorAccount->{mailType} eq "html")
          {
            $mailBody .= "      <tr>\n";
            $mailBody .= "        <td>$ledgerName</td><td>$account - $accountName</td><td>&nbsp;</td><td align=\"center\">$monitorAccount->{operator}</td><td align=\"right\">$monitorAccount->{greaterThan}</td><td align=\"right\">$balance</td>\n";
            $mailBody .= "      </tr>\n";
          }
        }
      }
      else
      {
        myDie(error => "Neither lessThan or greaterThan were specified!\n", configObj => $configObj);
      }
    }

    if ($monitorAccount->{mailType} eq "html")
    {
      $mailBody .= "    </table>\n";
      $mailBody .= "  </body>\n</html>\n";
    }

    if ($mailNeeded)
    {
      print $mailBody if ($debug);
      my @emails = split /,/, $monitorAccount->{emails};
      foreach my $email (@emails)
      {
        print "email = '$email'\n" if ($debug);
        sendEmail(to => $email, from => $fromUser, subject => "XIWA Monitor Accounts Update", body => $mailBody, mailServer => $mailServer, ctype => $ctype);
      }
      my @users = split /,/, $monitorAccount->{users};
      foreach my $user (@users)
      {
        # lookup the user info and get their email address
        my $userInfoObj = $authObj->getUserInfo(uname => $user);
        if ($authObj->error)
        {
          myDie(error => $authObj->errorMessage, configObj => $configObj);
        }
        if (!defined $userInfoObj)
        {
          myDie(error => "User '$user' does not exist!", configObj => $configObj);
        }
        elsif (!$userInfoObj->isValid())
        {
          myDie(error => $userInfoObj->errorMessage, configObj => $configObj);
        }
        else
        {
          (my $email = $userInfoObj->{email}) =~ s/\\@/@/;
          # make sure we are not sending duplicates.
          my $sendEmail = 1;
          foreach my $tmpEmail (@emails)
          {
            if ($tmpEmail eq $email)
            {
              print "suppressing duplicate e-mail to '$email'\n" if ($debug);
              $sendEmail = 0;
              last;
            }
          }
          if ($sendEmail)
          {
            print "email = '$email'\n" if ($debug);
            sendEmail(to => $email, from => $fromUser, subject => "XIWA Monitor Accounts Update", body => $mailBody, mailServer => $mailServer, ctype => $ctype);
          }
        }
      }
    }
  }
}

exit 0;

# myDie - Takes lang, encoding, error, configObj
sub myDie
{
  my %args = ( lang => 'en', encoding => 'iso-8859-1', error => "", configObj => undef, @_ );
  my $lang = $args{lang};
  my $encoding = $args{encoding};
  my $message = $args{error};
  my $configObj = $args{configObj};
  my $dateStamp = `/bin/date`;
  chomp $dateStamp;

  print("Error Occurred!\n");
  print($message);
  print("Have the Administrator check the error log ($dateStamp).\n");
  my $logObj = Portal::Log->new(dbHandle => $portalDB, langObj => $langObj);
  if ($logObj->error)
  {
    die $logObj->errorMessage;
  }
  my $hostname = `hostname -i`;
  chomp $hostname;
  $hostname =~ s/ //g;

  my $logEntry = Portal::Objects::LogEntry->new(action => 30, ipAddress => $hostname, extraInfo => $message, userId => 0, langObj => $langObj);
  if ($logEntry->error)
  {
    die $logEntry->errorMessage;
  }
  $logObj->newEntry(logEntry => $logEntry);
  if ($logObj->error)
  {
    die $logObj->errorMessage;
  }
  exit 1;
}

# takes: to, from, subject, body, mailServer, ctype as name => value pairs.
sub sendEmail
{
  my %args = ( to => "", from => "", subject => "", body => "", mailServer => "127.0.0.1", ctype => "text/plain", @_ );
  my $to = $args{to};
  my $from = $args{from};
  my $subject = $args{subject};
  my $body = $args{body};
  my $mailServer = $args{mailServer};
  my $ctype = $args{ctype};
  my $errorOutput = "";

  my $sender = new Mail::Sender { smtp => "$mailServer" };
  if (!ref ($sender))
  {
    $errorOutput .= "Error initializing e-mail connection with server via Mail::Sender Module!\n";
    $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n";
    $errorOutput .= "Note to Administrator:&nbsp; xiwa was sending a $ctype e-mail.\n";
    myDie(error => $errorOutput, configObj => $configObj);
  }
  $Mail::Sender::NO_X_MAILER = 1;  # turn off the X_Mailer entry that has the info about this module.
  if ($sender->Open( { from => "$from", to => "$to", subject => "$subject", headers => "MIME-Version: 1.0\r\nContent-type: $ctype\r\nContent-Transfer-Encoding: 7bit" }) < 0)
  {
    $errorOutput .= "Error opening e-mail connection with server via Mail::Sender Module!\n";
    $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n";
    $errorOutput .= "Note to Administrator:&nbsp; xiwa was sending a $ctype e-mail.\n";
    myDie(error => $errorOutput, configObj => $configObj);
  }
  if ($sender->Send($body) < 0)
  {
    $errorOutput .= "Error sending e-mail via Mail::Sender Module!\n";
    $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n";
    $errorOutput .= "Note to Administrator:&nbsp; xiwa was sending a $ctype e-mail.\n";
    myDie(error => $errorOutput, configObj => $configObj);
  }
  if ($sender->Close() < 0)
  {
    $errorOutput .= "Error sending e-mail via Mail::Sender Module!\n";
    $errorOutput .= "Error = \"" . $Mail::Sender::Error . "\"\n\n";
    $errorOutput .= "Note to Administrator:&nbsp; xiwa was sending a $ctype e-mail.\n";
    myDie(error => $errorOutput, configObj => $configObj);
  }
}
