git-svn-id: file:///svn-source/pmgr/branches/v0.3_work@977 97e9348a-65ac-dc4b-aefc-98561f571b83
784 lines
28 KiB
PHP
784 lines
28 KiB
PHP
<?php
|
|
class StatementEntry extends AppModel {
|
|
|
|
var $belongsTo = array(
|
|
'Transaction',
|
|
'Customer',
|
|
'Lease',
|
|
'Account',
|
|
|
|
// The charge to which this disbursement applies (if it is one)
|
|
'ChargeEntry' => array(
|
|
'className' => 'StatementEntry',
|
|
),
|
|
);
|
|
|
|
var $hasMany = array(
|
|
// The disbursements that apply to this charge (if it is one)
|
|
'DisbursementEntry' => array(
|
|
'className' => 'StatementEntry',
|
|
'foreignKey' => 'charge_entry_id',
|
|
'dependent' => true,
|
|
),
|
|
|
|
);
|
|
|
|
//var $default_log_level = array('log' => 30, 'show' => 15);
|
|
var $max_log_level = 19;
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: debit/creditTypes
|
|
*/
|
|
|
|
function debitTypes() {
|
|
return array('CHARGE', 'PAYMENT', 'REFUND');
|
|
}
|
|
|
|
function creditTypes() {
|
|
return array('DISBURSEMENT', 'WAIVER', 'REVERSAL', 'WRITEOFF', 'SURPLUS');
|
|
}
|
|
|
|
function voidTypes() {
|
|
return array('VOID');
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: chargeDisbursementFields
|
|
*/
|
|
|
|
function chargeDisbursementFields($sum = false, $entry_name = 'StatementEntry') {
|
|
$debits = $this->debitTypes();
|
|
$credits = $this->creditTypes();
|
|
$voids = $this->voidTypes();
|
|
|
|
foreach ($debits AS &$enum)
|
|
$enum = "'" . $enum . "'";
|
|
foreach ($credits AS &$enum)
|
|
$enum = "'" . $enum . "'";
|
|
foreach ($voids AS &$enum)
|
|
$enum = "'" . $enum . "'";
|
|
|
|
$debit_set = implode(", ", $debits);
|
|
$credit_set = implode(", ", $credits);
|
|
$void_set = implode(", ", $voids);
|
|
|
|
$fields = array
|
|
(
|
|
($sum ? 'SUM(' : '') .
|
|
"IF({$entry_name}.type IN ({$debit_set})," .
|
|
" {$entry_name}.amount, NULL)" .
|
|
($sum ? ')' : '') . ' AS charge' . ($sum ? 's' : ''),
|
|
|
|
($sum ? 'SUM(' : '') .
|
|
"IF({$entry_name}.type IN({$credit_set})," .
|
|
" {$entry_name}.amount, NULL)" .
|
|
($sum ? ')' : '') . ' AS disbursement' . ($sum ? 's' : ''),
|
|
|
|
($sum ? 'SUM(' : '') .
|
|
"IF({$entry_name}.type IN ({$debit_set}), 1," .
|
|
" IF({$entry_name}.type IN ({$credit_set}), -1, 0))" .
|
|
" * IF({$entry_name}.amount, {$entry_name}.amount, 0)" .
|
|
($sum ? ')' : '') . ' AS balance',
|
|
);
|
|
|
|
if ($sum)
|
|
$fields[] = "COUNT({$entry_name}.id) AS entries";
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: verifyStatementEntry
|
|
* - Verifies consistenty of new statement entry data
|
|
* (not in a pre-existing statement entry)
|
|
*/
|
|
function verifyStatementEntry($entry) {
|
|
$this->prFunctionLevel(10);
|
|
$this->prEnter(compact('entry'));
|
|
|
|
if (empty($entry['type']) ||
|
|
//empty($entry['effective_date']) ||
|
|
empty($entry['account_id']) ||
|
|
empty($entry['amount'])
|
|
) {
|
|
return $this->prReturn(false);
|
|
}
|
|
|
|
return $this->prReturn(true);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addStatementEntry
|
|
* - Inserts new Statement Entry into the database
|
|
*/
|
|
function addStatementEntry($entry) {
|
|
$this->prEnter(compact('entry'));
|
|
|
|
$ret = array('data' => $entry);
|
|
if (!$this->verifyStatementEntry($entry))
|
|
return $this->prReturn(array('error' => true, 'verify_data' => $entry) + $ret);
|
|
|
|
$this->create();
|
|
if (!$this->save($entry))
|
|
return $this->prReturn(array('error' => true, 'save_data' => $entry) + $ret);
|
|
|
|
$ret['statement_entry_id'] = $this->id;
|
|
return $this->prReturn($ret + array('error' => false));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: waive
|
|
* - Waives the charge balance
|
|
*
|
|
*/
|
|
function waive($id, $stamp = null) {
|
|
$this->prEnter(compact('id', 'stamp'));
|
|
|
|
// Get the basic information about the entry to be waived.
|
|
$this->recursive = -1;
|
|
$charge = $this->read(null, $id);
|
|
$charge = $charge['StatementEntry'];
|
|
|
|
if ($charge['type'] !== 'CHARGE')
|
|
$this->INTERNAL_ERROR("Waiver item is not CHARGE.");
|
|
|
|
// Query the stats to get the remaining balance
|
|
$stats = $this->stats($id);
|
|
|
|
// Build a transaction
|
|
$waiver = array('Transaction' => array(), 'Entry' => array());
|
|
$waiver['Transaction']['stamp'] = $stamp;
|
|
$waiver['Transaction']['comment'] = "Charge Waiver";
|
|
|
|
// Add the charge waiver
|
|
$waiver['Entry'][] =
|
|
array('amount' => $stats['Charge']['balance'],
|
|
'comment' => null,
|
|
);
|
|
|
|
// Record the waiver transaction
|
|
return $this->prReturn($this->Transaction->addWaiver
|
|
($waiver, $id, $charge['customer_id'], $charge['lease_id']));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reversable
|
|
* - Returns true if the charge can be reversed; false otherwise
|
|
*/
|
|
function reversable($id) {
|
|
$this->prEnter(compact('id'));
|
|
|
|
if (empty($id))
|
|
return $this->prReturn(false);
|
|
|
|
// Verify the item is an actual charge
|
|
$this->id = $id;
|
|
$charge_type = $this->field('type');
|
|
if ($charge_type !== 'CHARGE')
|
|
return $this->prReturn(false);
|
|
|
|
// Determine anything reconciled against the charge
|
|
$reverse_transaction_id = $this->field('reverse_transaction_id');
|
|
if (!empty($reverse_transaction_id))
|
|
return $this->prReturn(false);
|
|
|
|
return $this->prReturn(true);
|
|
}
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reverse
|
|
* - Reverses the charges
|
|
*/
|
|
function reverse($id, $stamp = null, $comment) {
|
|
$this->prEnter(compact('id', 'stamp'));
|
|
|
|
// Verify the item can be reversed
|
|
if (!$this->reversable($id))
|
|
$this->INTERNAL_ERROR("Item is not reversable.");
|
|
|
|
// Get the basic information about this charge
|
|
$charge = $this->find('first', array('contain' => true));
|
|
//$charge = $charge['StatementEntry'];
|
|
|
|
// Query the stats to get the remaining balance
|
|
$stats = $this->stats($id);
|
|
$charge['paid'] = $stats['Charge']['disbursement'];
|
|
|
|
// Record the reversal transaction
|
|
$result = $this->Transaction->addReversal
|
|
($charge, $stamp, $comment ? $comment : 'Charge Reversal');
|
|
|
|
if (empty($result['error'])) {
|
|
// Mark the charge as reversed
|
|
$this->id = $id;
|
|
$this->saveField('reverse_transaction_id', $result['transaction_id']);
|
|
}
|
|
|
|
return $this->prReturn($result);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reconciledSet
|
|
* - Returns the set of entries satisfying the given conditions,
|
|
* along with any entries that they reconcile
|
|
*/
|
|
function reconciledSetQuery($set, $query) {
|
|
$this->queryInit($query);
|
|
|
|
if (in_array($set, $this->debitTypes()))
|
|
$query['link']['DisbursementEntry'] = array('fields' => array("SUM(DisbursementEntry.amount) AS reconciled"));
|
|
elseif (in_array($set, $this->creditTypes()))
|
|
$query['link']['ChargeEntry'] = array('fields' => array("SUM(ChargeEntry.amount) AS reconciled"));
|
|
else
|
|
die("INVALID RECONCILE SET");
|
|
|
|
$query['conditions'][] = array('StatementEntry.type' => $set);
|
|
$query['group'] = 'StatementEntry.id';
|
|
|
|
return $query;
|
|
}
|
|
|
|
function reconciledSet($set, $query = null, $unrec = false, $if_rec_include_partial = false) {
|
|
//$this->prFunctionLevel(array('log' => 16, 'show' => 10));
|
|
$this->prEnter(compact('set', 'query', 'unrec', 'if_rec_include_partial'));
|
|
$lquery = $this->reconciledSetQuery($set, $query);
|
|
$result = $this->find('all', $lquery);
|
|
|
|
$this->pr(20, compact('lquery', 'result'));
|
|
|
|
$resultset = array();
|
|
foreach ($result AS $i => $entry) {
|
|
$this->pr(25, compact('entry'));
|
|
if (!empty($entry[0]))
|
|
$entry['StatementEntry'] = $entry[0] + $entry['StatementEntry'];
|
|
unset($entry[0]);
|
|
|
|
$entry['StatementEntry']['balance'] =
|
|
$entry['StatementEntry']['amount'] - $entry['StatementEntry']['reconciled'];
|
|
|
|
// Since HAVING isn't a builtin feature of CakePHP,
|
|
// we'll have to post-process to get the desired entries
|
|
|
|
if ($entry['StatementEntry']['balance'] == 0)
|
|
$reconciled = true;
|
|
elseif ($entry['StatementEntry']['reconciled'] == 0)
|
|
$reconciled = false;
|
|
else // Partial disbursement; depends on unrec
|
|
$reconciled = (!$unrec && $if_rec_include_partial);
|
|
|
|
// Add to the set, if it's been requested
|
|
if ($reconciled == !$unrec)
|
|
$resultset[] = $entry;
|
|
}
|
|
|
|
return $this->prReturn(array('entries' => $resultset,
|
|
'summary' => $this->stats(null, $query)));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reconciledEntries
|
|
* - Returns a list of entries that reconcile against the given entry.
|
|
* (such as disbursements towards a charge).
|
|
*/
|
|
function reconciledEntriesQuery($id, $query = null) {
|
|
$this->queryInit($query, false);
|
|
|
|
$this->id = $id;
|
|
$this->recursive = -1;
|
|
$this->read();
|
|
|
|
$query['conditions'][] = array('StatementEntry.id' => $id);
|
|
|
|
if (in_array($this->data['StatementEntry']['type'], $this->debitTypes())) {
|
|
$query['link']['DisbursementEntry'] = array();
|
|
$query['conditions'][] = array('DisbursementEntry.id !=' => null);
|
|
}
|
|
if (in_array($this->data['StatementEntry']['type'], $this->creditTypes())) {
|
|
$query['link']['ChargeEntry'] = array();
|
|
$query['conditions'][] = array('ChargeEntry.id !=' => null);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
function reconciledEntries($id, $query = null) {
|
|
$this->prEnter(compact('id', 'query'));
|
|
$lquery = $this->reconciledEntriesQuery($id, $query);
|
|
|
|
$result = $this->find('all', $lquery);
|
|
foreach (array_keys($result) AS $i)
|
|
unset($result[$i]['StatementEntry']);
|
|
|
|
return $this->prReturn(array('entries' => $result));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: outstandingDebits
|
|
* - Determines all debit types that have not yet been resolved.
|
|
* The name is a bit dumb, but it means any statement entry type
|
|
* that a positive customer balance could be used to offset. In
|
|
* other words, entries that are still in need of matching
|
|
* disbursements. Most notably, this means charges but could
|
|
* also mean things like refunds as well.
|
|
*/
|
|
|
|
function outstandingDebits($query = null, $customer_id = null,
|
|
$lease_id = null, $debit_types = null)
|
|
{
|
|
$this->prEnter(compact('query', 'lease_id',
|
|
'customer_id', 'charge_ids',
|
|
'debit_types'));
|
|
$this->queryInit($query);
|
|
|
|
if (empty($debit_types))
|
|
$debit_types = $this->debitTypes();
|
|
|
|
if (!empty($customer_id))
|
|
$query['conditions'][] = array('StatementEntry.customer_id' => $customer_id);
|
|
|
|
if (!empty($lease_id))
|
|
$query['conditions'][] = array('StatementEntry.lease_id' => $lease_id);
|
|
|
|
/* if (isset($charge_ids)) { */
|
|
/* // REVISIT <AP> 20100330: */
|
|
/* // Not using $query here, as this code was extracted from its */
|
|
/* // original location in assignCredits, and so I'm keeping the */
|
|
/* // logic consistent. It does seem, however, that we shouldn't */
|
|
/* // be ignoring $query if passed in. I'm sure this won't be */
|
|
/* // looked at until someone _does_ pass $query in (and it break), */
|
|
/* // so hopefully at that time, we can understand what needs to */
|
|
/* // happen in that case (requirements are not clear at present). */
|
|
/* $lquery = array('contain' => false, */
|
|
/* 'conditions' => array('StatementEntry.id' => $charge_ids)); */
|
|
/* } else { */
|
|
/* $lquery = $query; */
|
|
/* // If we're working with a specific lease, then limit charges to it */
|
|
/* if (!empty($lease_id)) */
|
|
/* $lquery['conditions'][] = array('StatementEntry.lease_id' => $lease_id); */
|
|
/* } */
|
|
|
|
if (empty($query['order']))
|
|
$query['order'] = 'StatementEntry.effective_date ASC';
|
|
|
|
$debits = array();
|
|
foreach ($debit_types AS $dtype) {
|
|
$rset = $this->reconciledSet($dtype, $query, true);
|
|
$entries = $rset['entries'];
|
|
$debits = array_merge($debits, $entries);
|
|
$this->pr(18, compact('dtype', 'entries'), "Outstanding Debit Entries");
|
|
}
|
|
|
|
return $this->prReturn($debits);
|
|
}
|
|
|
|
function outstandingCharges($query = null, $customer_id = null, $lease_id = null) {
|
|
return $this->outstandingDebits($query, $customer_id, $lease_id, array('CHARGE'));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: assignCredits
|
|
* - Assigns all credits to existing charges
|
|
*
|
|
* REVISIT <AP>: 20090726
|
|
* This algorithm shouldn't be hardcoded. We need to allow
|
|
* the user to specify how disbursements should be applied.
|
|
*
|
|
*/
|
|
function assignCredits($query = null, $receipt_id = null,
|
|
$charge_ids = null, $disbursement_type = null,
|
|
$customer_id = null, $lease_id = null)
|
|
{
|
|
//$this->prFunctionLevel(25);
|
|
$this->prEnter(compact('query', 'receipt_id',
|
|
'charge_ids', 'disbursement_type',
|
|
'customer_id', 'lease_id'));
|
|
$this->queryInit($query);
|
|
|
|
if (empty($disbursement_type))
|
|
$disbursement_type = 'DISBURSEMENT';
|
|
|
|
$ret = array();
|
|
|
|
// First, find all known credits, unless this call is to make
|
|
// credit adjustments to a specific charge
|
|
if (empty($receipt_id)) {
|
|
|
|
if (!empty($charge_ids))
|
|
$this->INTERNAL_ERROR("Charge IDs, yet no corresponding receipt");
|
|
|
|
$lquery = $query;
|
|
if (!empty($customer_id))
|
|
$lquery['conditions'][] = array('StatementEntry.customer_id' => $customer_id);
|
|
|
|
$lquery['conditions'][] = array('StatementEntry.type' => 'SURPLUS');
|
|
// REVISIT <AP>: 20090804
|
|
// We need to ensure that we're using surplus credits ONLY from either
|
|
// the given lease, or those that do not apply to any specific lease.
|
|
// However, by doing this, it forces any lease surplus amounts to
|
|
// remain frozen with that lease until either there is a lease charge,
|
|
// we refund the money, or we "promote" that surplus to the customer
|
|
// level and out of the leases direct control.
|
|
// That seems like a pain. Perhaps we should allow any customer
|
|
// surplus to be used on any customer charge.
|
|
$lquery['conditions'][] =
|
|
array('OR' =>
|
|
array(array('StatementEntry.lease_id' => null),
|
|
(!empty($lease_id)
|
|
? array('StatementEntry.lease_id' => $lease_id)
|
|
: array()),
|
|
));
|
|
$lquery['order'][] = 'StatementEntry.effective_date ASC';
|
|
$credits = $this->find('all', $lquery);
|
|
$this->pr(18, compact('credits'),
|
|
"Credits Established");
|
|
}
|
|
else {
|
|
// Establish credit from the (newly added) receipt
|
|
$lquery =
|
|
array('link' =>
|
|
array('StatementEntry',
|
|
'LedgerEntry' =>
|
|
array('conditions' =>
|
|
array('LedgerEntry.account_id <> Transaction.account_id')
|
|
),
|
|
),
|
|
'conditions' => array('Transaction.id' => $receipt_id),
|
|
'fields' => array('Transaction.id', 'Transaction.stamp', 'Transaction.amount'),
|
|
);
|
|
$receipt_credit = $this->Transaction->find('first', $lquery);
|
|
if (!$receipt_credit)
|
|
$this->INTERNAL_ERROR("Unable to locate receipt.");
|
|
|
|
$stats = $this->Transaction->stats($receipt_id);
|
|
$receipt_credit['balance'] = $stats['undisbursed'];
|
|
|
|
$receipt_credit['receipt'] = true;
|
|
$credits = array($receipt_credit);
|
|
$this->pr(18, compact('credits'),
|
|
"Receipt Credit Added");
|
|
}
|
|
|
|
// Now find all unpaid charges, using either the specific set
|
|
// of charges given, or all outstanding charges based on the
|
|
// query, customer and/or lease
|
|
if (!empty($charge_ids)) {
|
|
$this->INTERNAL_ERROR("PERHAPS IMPLEMENTED - THOUGH NEVER TESTED");
|
|
$lquery = $query;
|
|
$lquery['conditions'][] = array('StatementEntry.id' => $charge_ids);
|
|
$charges = $this->reconciledSet('CHARGE', $query, true);
|
|
} else {
|
|
$charges = $this->outstandingDebits($query, $customer_id, $lease_id);
|
|
}
|
|
|
|
// Work through all unpaid charges, applying disbursements as we go
|
|
foreach ($charges AS $charge) {
|
|
$this->pr(20, compact('charge'),
|
|
'Process Charge');
|
|
|
|
$charge['balance'] = $charge['StatementEntry']['balance'];
|
|
|
|
// Use explicit credits before using the new receipt credit
|
|
foreach ($credits AS &$credit) {
|
|
if (empty($charge['balance']))
|
|
break;
|
|
if ($charge['balance'] < 0)
|
|
$this->INTERNAL_ERROR("Negative Charge Balance");
|
|
|
|
if (!isset($credit['balance']))
|
|
$credit['balance'] = $credit['StatementEntry']['amount'];
|
|
|
|
if (empty($credit['balance']))
|
|
continue;
|
|
if ($credit['balance'] < 0)
|
|
$this->INTERNAL_ERROR("Negative Credit Balance");
|
|
|
|
$this->pr(20, compact('charge'),
|
|
'Attempt Charge Reconciliation');
|
|
|
|
if (empty($credit['receipt']))
|
|
$disbursement_account_id = $credit['StatementEntry']['account_id'];
|
|
else
|
|
$disbursement_account_id = $credit['LedgerEntry']['account_id'];
|
|
|
|
// REVISIT <AP>: 20090811
|
|
// Need to come up with a better strategy for handling
|
|
// concessions. For now, just restricting concessions
|
|
// to apply only towards rent will resolve the most
|
|
// predominant (or only) needed usage case.
|
|
if ($disbursement_account_id == $this->Account->concessionAccountID() &&
|
|
$charge['StatementEntry']['account_id'] != $this->Account->rentAccountID())
|
|
continue;
|
|
|
|
// Set the disbursement amount to the maximum amount
|
|
// possible without exceeding the charge or credit balance
|
|
$disbursement_amount = min($charge['balance'], $credit['balance']);
|
|
if (!isset($credit['applied']))
|
|
$credit['applied'] = 0;
|
|
|
|
$credit['applied'] += $disbursement_amount;
|
|
$credit['balance'] -= $disbursement_amount;
|
|
|
|
$this->pr(20, compact('credit'),
|
|
($credit['balance'] > 0 ? 'Utilized' : 'Exhausted') .
|
|
(empty($credit['receipt']) ? ' Credit' : ' Receipt'));
|
|
|
|
if (strtotime($charge['StatementEntry']['effective_date']) >
|
|
strtotime($credit['StatementEntry']['effective_date']))
|
|
$disbursement_edate = $charge['StatementEntry']['effective_date'];
|
|
else
|
|
$disbursement_edate = $credit['StatementEntry']['effective_date'];
|
|
|
|
if (empty($credit['receipt'])) {
|
|
// Explicit Credit
|
|
$result = $this->Transaction->addTransactionEntries
|
|
(array('include_ledger_entry' => true,
|
|
'include_statement_entry' => true),
|
|
array('type' => 'INVOICE',
|
|
'id' => $credit['StatementEntry']['transaction_id'],
|
|
'account_id' => $this->Account->accountReceivableAccountID(),
|
|
'crdr' => 'CREDIT',
|
|
'customer_id' => $charge['StatementEntry']['customer_id'],
|
|
'lease_id' => $charge['StatementEntry']['lease_id'],
|
|
),
|
|
array
|
|
(array('type' => $disbursement_type,
|
|
'effective_date' => $disbursement_edate,
|
|
'account_id' => $credit['StatementEntry']['account_id'],
|
|
'amount' => $disbursement_amount,
|
|
'charge_entry_id' => $charge['StatementEntry']['id'],
|
|
),
|
|
));
|
|
|
|
$ret['Disbursement'][] = $result;
|
|
if ($result['error'])
|
|
$ret['error'] = true;
|
|
}
|
|
else {
|
|
// Receipt Credit
|
|
|
|
if (strtotime($charge['StatementEntry']['effective_date']) >
|
|
strtotime($credit['Transaction']['stamp']))
|
|
$disbursement_edate = $charge['StatementEntry']['effective_date'];
|
|
else
|
|
$disbursement_edate = $credit['Transaction']['stamp'];
|
|
|
|
// Add a disbursement that uses the available credit to pay the charge
|
|
$disbursement =
|
|
array('type' => $disbursement_type,
|
|
'effective_date' => $disbursement_edate,
|
|
'amount' => $disbursement_amount,
|
|
'account_id' => $credit['LedgerEntry']['account_id'],
|
|
'transaction_id' => $credit['Transaction']['id'],
|
|
'customer_id' => $charge['StatementEntry']['customer_id'],
|
|
'lease_id' => $charge['StatementEntry']['lease_id'],
|
|
'charge_entry_id' => $charge['StatementEntry']['id'],
|
|
'comment' => null,
|
|
);
|
|
|
|
$this->pr(20, compact('disbursement'), 'New Disbursement Entry');
|
|
$result = $this->addStatementEntry($disbursement);
|
|
$ret['Disbursement'][] = $result;
|
|
if ($result['error'])
|
|
$ret['error'] = true;
|
|
}
|
|
|
|
// Adjust the charge balance to reflect the new disbursement
|
|
$charge['balance'] -= $disbursement_amount;
|
|
if ($charge['balance'] < 0)
|
|
die("HOW DID WE GET A NEGATIVE CHARGE AMOUNT?");
|
|
|
|
if ($charge['balance'] <= 0)
|
|
$this->pr(20, 'Fully Paid Charge');
|
|
}
|
|
// Break the $credit reference to avoid future problems
|
|
unset($credit);
|
|
}
|
|
|
|
$this->pr(18, compact('credits'),
|
|
'Disbursements complete');
|
|
|
|
// Clean up any explicit credits that have been used
|
|
foreach ($credits AS $credit) {
|
|
if (!empty($credit['receipt']))
|
|
continue;
|
|
|
|
if (empty($credit['applied']))
|
|
continue;
|
|
|
|
if ($credit['balance'] > 0) {
|
|
$this->pr(20, compact('credit'),
|
|
'Update Credit Entry');
|
|
|
|
$this->id = $credit['StatementEntry']['id'];
|
|
$this->saveField('amount', $credit['balance']);
|
|
}
|
|
else {
|
|
$this->pr(20, compact('credit'),
|
|
'Delete Exhausted Credit Entry');
|
|
|
|
$this->delete($credit['StatementEntry']['id'], false);
|
|
}
|
|
}
|
|
|
|
// Check for any implicit receipt credits, converting
|
|
// into explicit credits if there is a remaining balance.
|
|
foreach ($credits AS $credit) {
|
|
if (empty($credit['receipt']))
|
|
continue;
|
|
|
|
if (empty($credit['balance']))
|
|
continue;
|
|
|
|
// See if there is an existing explicit credit
|
|
// for this transaction.
|
|
$explicit_credit = $this->find
|
|
('first', array('contain' => false,
|
|
'conditions' =>
|
|
array(array('transaction_id' => $credit['Transaction']['id']),
|
|
array('type' => 'SURPLUS')),
|
|
));
|
|
|
|
if (!empty($explicit_credit)) {
|
|
// REVISIT <AP>: 20090815
|
|
// Testing whether or not this case occurs
|
|
$this->INTERNAL_ERROR('Existing explicit credit unexpected');
|
|
|
|
// Since there IS an existing explicit credit, we must update
|
|
// its balance instead of creating a new one, since it has
|
|
// already been incorporated in the overall credit balance.
|
|
// If we were to create a new one, we would erroneously create
|
|
// an excess of credit available.
|
|
$this->pr(18, compact('explicit_credit', 'credit'),
|
|
'Update existing explicit credit');
|
|
$EC = new StatementEntry();
|
|
$EC->id = $explicit_credit['StatementEntry']['id'];
|
|
$EC->saveField('amount', $credit['balance']);
|
|
continue;
|
|
}
|
|
|
|
if (!empty($ret['receipt_balance']))
|
|
$this->INTERNAL_ERROR('Only one receipt expected in assignCredits');
|
|
|
|
// Give caller the information necessary to create an explicit
|
|
// credit from the passed receipt, which we've not exhausted.
|
|
$this->pr(18, compact('credit'), 'Convert to explicit credit');
|
|
$ret['receipt_balance'] = $credit['balance'];
|
|
}
|
|
|
|
return $this->prReturn($ret + array('error' => false));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: stats
|
|
* - Returns summary data from the requested statement entry
|
|
*/
|
|
function stats($id = null, $query = null) {
|
|
//$this->prFunctionLevel(array('log' => 16, 'show' => 10));
|
|
$this->prEnter(compact('id', 'query'));
|
|
|
|
$this->queryInit($query);
|
|
unset($query['group']);
|
|
|
|
$stats = array();
|
|
if (isset($id))
|
|
$query['conditions'][] = array('StatementEntry.id' => $id);
|
|
|
|
$types = array('Charge', 'Disbursement');
|
|
foreach ($types AS $type_index => $this_name) {
|
|
$that_name = $types[($type_index + 1) % 2];
|
|
if ($this_name === 'Charge') {
|
|
$this_types = $this->debitTypes();
|
|
$that_types = $this->creditTypes();
|
|
} else {
|
|
$this_types = $this->creditTypes();
|
|
$that_types = $this->debitTypes();
|
|
}
|
|
|
|
$this_query = $query;
|
|
$this_query['fields'] = array();
|
|
$this_query['fields'][] = "SUM(StatementEntry.amount) AS total";
|
|
$this_query['conditions'][] = array('StatementEntry.type' => $this_types);
|
|
$result = $this->find('first', $this_query);
|
|
$stats[$this_name] = $result[0];
|
|
|
|
$this->pr(17, compact('this_query', 'result'), $this_name.'s');
|
|
|
|
// Tally the different types that result in credits towards the charges
|
|
$stats[$this_name]['reconciled'] = 0;
|
|
foreach ($that_types AS $that_type) {
|
|
$lc_that_type = strtolower($that_type);
|
|
$that_query = $this_query;
|
|
$that_query['link']["{$that_name}Entry"] = array('fields' => array());
|
|
$that_query['fields'] = array();
|
|
if ($this_name == 'Charge')
|
|
$that_query['fields'][] = "COALESCE(SUM(${that_name}Entry.amount),0) AS $lc_that_type";
|
|
else
|
|
$that_query['fields'][] = "COALESCE(SUM(StatementEntry.amount), 0) AS $lc_that_type";
|
|
$that_query['conditions'][] = array("{$that_name}Entry.type" => $that_type);
|
|
$result = $this->find('first', $that_query);
|
|
$stats[$this_name] += $result[0];
|
|
|
|
$this->pr(17, compact('that_query', 'result'), "{$this_name}s: $that_type");
|
|
$stats[$this_name]['reconciled'] += $stats[$this_name][$lc_that_type];
|
|
}
|
|
|
|
// Compute balance information for charges
|
|
$stats[$this_name]['balance'] =
|
|
$stats[$this_name]['total'] - $stats[$this_name]['reconciled'];
|
|
if (!isset($stats[$this_name]['balance']))
|
|
$stats[$this_name]['balance'] = 0;
|
|
}
|
|
|
|
// 'balance' is simply the difference between
|
|
// the balances of charges and disbursements
|
|
$stats['balance'] = $stats['Charge']['balance'] - $stats['Disbursement']['balance'];
|
|
if (!isset($stats['balance']))
|
|
$stats['balance'] = 0;
|
|
|
|
// 'account_balance' is really only relevant to
|
|
// callers that have requested charge and disbursement
|
|
// stats with respect to a particular account.
|
|
// It represents the difference between inflow
|
|
// and outflow from that account.
|
|
$stats['account_balance'] = $stats['Charge']['reconciled'] - $stats['Disbursement']['total'];
|
|
if (!isset($stats['account_balance']))
|
|
$stats['account_balance'] = 0;
|
|
|
|
return $this->prReturn($stats);
|
|
}
|
|
|
|
} |