git-svn-id: file:///svn-source/pmgr/branches/invoice_receipt_20090629/site@350 97e9348a-65ac-dc4b-aefc-98561f571b83
526 lines
19 KiB
PHP
526 lines
19 KiB
PHP
<?php
|
|
class LedgerEntry extends AppModel {
|
|
|
|
var $name = 'LedgerEntry';
|
|
var $validate = array(
|
|
'id' => array('numeric'),
|
|
'transaction_id' => array('numeric'),
|
|
'amount' => array('money')
|
|
);
|
|
|
|
var $hasMany = array(
|
|
'DebitReconciliation' => array(
|
|
'className' => 'Reconciliation',
|
|
'foreignKey' => 'debit_ledger_entry_id',
|
|
),
|
|
'CreditReconciliation' => array(
|
|
'className' => 'Reconciliation',
|
|
'foreignKey' => 'credit_ledger_entry_id',
|
|
),
|
|
);
|
|
var $belongsTo = array(
|
|
'MonetarySource',
|
|
'Transaction',
|
|
'Customer',
|
|
'Lease',
|
|
|
|
'DebitLedger' => array(
|
|
'className' => 'Ledger',
|
|
'foreignKey' => 'debit_ledger_id',
|
|
),
|
|
'CreditLedger' => array(
|
|
'className' => 'Ledger',
|
|
'foreignKey' => 'credit_ledger_id',
|
|
),
|
|
|
|
'Ledger' => array(
|
|
'foreignKey' => false,
|
|
// conditions will be used when JOINing tables
|
|
// (such as find with LinkableBehavior)
|
|
'conditions' => array('OR' =>
|
|
array('%{MODEL_ALIAS}.debit_ledger_id = Ledger.id',
|
|
'%{MODEL_ALIAS}.credit_ledger_id = Ledger.id')),
|
|
|
|
// finderQuery will be used when tables are put
|
|
// together across several querys, not with JOIN.
|
|
// (such as find with ContainableBehavior)
|
|
'finderQuery' => 'NOT-IMPLEMENTED',
|
|
|
|
'counterQuery' => ''
|
|
),
|
|
|
|
);
|
|
|
|
var $hasAndBelongsToMany = array(
|
|
'DebitReconciliationLedgerEntry' => array(
|
|
'className' => 'LedgerEntry',
|
|
'joinTable' => 'reconciliations',
|
|
'foreignKey' => 'credit_ledger_entry_id',
|
|
'associationForeignKey' => 'debit_ledger_entry_id',
|
|
),
|
|
// STUPID CakePHP bug screws up when using Containable
|
|
// and CLASS contains CLASS. This extra HABTM give the
|
|
// option of multiple depths on one CLASS, since there
|
|
// isn't an alias specification for Containable.
|
|
'DebitReconciliationLedgerEntry2' => array(
|
|
'className' => 'LedgerEntry',
|
|
'joinTable' => 'reconciliations',
|
|
'foreignKey' => 'credit_ledger_entry_id',
|
|
'associationForeignKey' => 'debit_ledger_entry_id',
|
|
),
|
|
'CreditReconciliationLedgerEntry' => array(
|
|
'className' => 'LedgerEntry',
|
|
'joinTable' => 'reconciliations',
|
|
'foreignKey' => 'debit_ledger_entry_id',
|
|
'associationForeignKey' => 'credit_ledger_entry_id',
|
|
),
|
|
);
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: conditionEntryAsCreditOrDebit
|
|
* - returns the condition necessary to match a set of
|
|
* Ledgers to all related LedgerEntries
|
|
*/
|
|
function conditionEntryAsCreditOrDebit($ledger_ids) {
|
|
return array('OR' =>
|
|
array(array('debit_ledger_id' => $ledger_ids),
|
|
array('credit_ledger_id' => $ledger_ids)));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: ledgerContext query helpers
|
|
* - Returns parameters necessary to generate a query which
|
|
* puts ledger entries into the context of a ledger. Since
|
|
* debit/credit depends on the account type, it is required
|
|
* as an argument for each function to avoid having to
|
|
* query the ledger/account to find it out.
|
|
*/
|
|
function ledgerContextFields($ledger_id = null, $account_type = null) {
|
|
$fields = array('id', 'effective_date', 'through_date',
|
|
'lease_id', 'customer_id', 'comment', 'amount');
|
|
|
|
if (isset($ledger_id)) {
|
|
$fields[] = ("IF(LedgerEntry.debit_ledger_id = $ledger_id," .
|
|
" LedgerEntry.amount, NULL) AS debit");
|
|
$fields[] = ("IF(LedgerEntry.credit_ledger_id = $ledger_id," .
|
|
" LedgerEntry.amount, NULL) AS credit");
|
|
|
|
if (isset($account_type)) {
|
|
if (in_array($account_type, array('ASSET', 'EXPENSE')))
|
|
$ledger_type = 'debit';
|
|
else
|
|
$ledger_type = 'credit';
|
|
|
|
$fields[] = ("(IF(LedgerEntry.{$ledger_type}_ledger_id = $ledger_id," .
|
|
" 1, -1) * LedgerEntry.amount) AS balance");
|
|
}
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
function ledgerContextFields2($ledger_id = null, $account_id = null, $account_type = null) {
|
|
$fields = array('id', 'effective_date', 'through_date', 'comment', 'amount');
|
|
|
|
if (isset($ledger_id)) {
|
|
$fields[] = ("IF(LedgerEntry.debit_ledger_id = $ledger_id," .
|
|
" SUM(LedgerEntry.amount), NULL) AS debit");
|
|
$fields[] = ("IF(LedgerEntry.credit_ledger_id = $ledger_id," .
|
|
" SUM(LedgerEntry.amount), NULL) AS credit");
|
|
|
|
if (isset($account_id) || isset($account_type)) {
|
|
$Account = new Account();
|
|
$account_ftype = $Account->fundamentalType($account_id ? $account_id : $account_type);
|
|
$fields[] = ("(IF(LedgerEntry.{$account_ftype}_ledger_id = $ledger_id," .
|
|
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
|
|
}
|
|
}
|
|
elseif (isset($account_id)) {
|
|
$fields[] = ("IF(DebitLedger.account_id = $account_id," .
|
|
" SUM(LedgerEntry.amount), NULL) AS debit");
|
|
$fields[] = ("IF(CreditLedger.account_id = $account_id," .
|
|
" SUM(LedgerEntry.amount), NULL) AS credit");
|
|
|
|
$Account = new Account();
|
|
$account_ftype = ucfirst($Account->fundamentalType($account_id));
|
|
$fields[] = ("(IF({$account_ftype}Ledger.account_id = $account_id," .
|
|
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
|
|
function ledgerContextConditions($ledger_id, $account_type) {
|
|
if (isset($ledger_id)) {
|
|
return array
|
|
('OR' =>
|
|
array(array('LedgerEntry.debit_ledger_id' => $ledger_id),
|
|
array('LedgerEntry.credit_ledger_id' => $ledger_id)),
|
|
);
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: findInLedgerContext
|
|
* - Returns an array of ledger entries that belong to a given ledger.
|
|
* There is extra logic to also figure out whether the ledger_entry
|
|
* amount is either a credit, or a debit, depending on how it was
|
|
* written into the ledger, as well as whether the amount increases or
|
|
* decreases the balance depending on the particular account type of
|
|
* the ledger.
|
|
*/
|
|
function findInLedgerContext($ledger_id, $account_type, $cond = null, $link = null) {
|
|
if (!isset($link))
|
|
$link = array('Transaction');
|
|
|
|
if (!isset($cond))
|
|
$cond = array();
|
|
|
|
$fields = $this->ledgerContextFields($ledger_id, $account_type);
|
|
$cond[] = $this->ledgerContextConditions($ledger_id, $account_type);
|
|
$order = array('Transaction.stamp');
|
|
|
|
$entries = $this->find
|
|
('all',
|
|
array('link' => $link,
|
|
'fields' => $fields,
|
|
'conditions' => $cond,
|
|
'order' => $order,
|
|
));
|
|
|
|
return $entries;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: findReconciledLedgerEntries
|
|
* - Returns ledger entries that are reconciled to the given entry.
|
|
* (such as payments towards a charge).
|
|
*/
|
|
|
|
function findReconciledLedgerEntries($id = null, $fundamental_type = null) {
|
|
foreach (($fundamental_type
|
|
? array($fundamental_type)
|
|
: array('debit', 'credit')) AS $fund) {
|
|
$ucfund = ucfirst($fund);
|
|
$reconciled[$fund]['entry'] = $this->find
|
|
('all', array
|
|
('link' => array
|
|
("ReconciliationLedgerEntry" => array
|
|
('class' => "{$ucfund}ReconciliationLedgerEntry",
|
|
'fields' => array
|
|
('id',
|
|
"COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'",
|
|
"LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
|
|
),
|
|
),
|
|
),
|
|
'group' => ("ReconciliationLedgerEntry.id"),
|
|
'conditions' => array('LedgerEntry.id' => $id),
|
|
'fields' => array(),
|
|
));
|
|
//pr($reconciled);
|
|
$balance = 0;
|
|
foreach ($reconciled[$fund]['entry'] AS &$entry) {
|
|
$entry = array_merge($entry["ReconciliationLedgerEntry"], $entry[0]);
|
|
$balance += $entry['balance'];
|
|
}
|
|
$reconciled[$fund]['balance'] = $balance;
|
|
}
|
|
|
|
return $reconciled;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reverse
|
|
* - Reverses the ledger entry
|
|
*/
|
|
|
|
function reverse1($id, $amount = null, $transaction_id = null, $rec_id = null) {
|
|
/* pr(array('LedgerEntry::reverse', */
|
|
/* compact('id', 'amount', 'transaction_id', 'rec_id'))); */
|
|
|
|
// Get the LedgerEntry and related fields
|
|
$entry = $this->find
|
|
('first',
|
|
array('contain' => array('MonetarySource.id',
|
|
'Transaction.id',
|
|
'DebitLedger.id',
|
|
'DebitLedger.account_id',
|
|
'CreditLedger.id',
|
|
'CreditLedger.account_id',
|
|
'DebitReconciliationLedgerEntry'
|
|
/* => */
|
|
/* array('DebitLedger.id', */
|
|
/* 'DebitLedger.account_id', */
|
|
/* 'CreditLedger.id', */
|
|
/* 'CreditLedger.account_id', */
|
|
/* ) */
|
|
,
|
|
'CreditReconciliationLedgerEntry'
|
|
/* => */
|
|
/* array('DebitLedger.id', */
|
|
/* 'DebitLedger.account_id', */
|
|
/* 'CreditLedger.id', */
|
|
/* 'CreditLedger.account_id', */
|
|
/* ) */
|
|
,
|
|
'Customer.id',
|
|
'Lease.id',
|
|
),
|
|
|
|
'fields' => array('LedgerEntry.*'),
|
|
|
|
'conditions' => array(array('LedgerEntry.id' => $id),
|
|
/* array('NOT' => */
|
|
/* array('OR' => */
|
|
/* array(array('DebitReconciliationLedgerEntry.id' => $rec_id), */
|
|
/* array('CreditReconciliationLedgerEntry.id' => $rec_id), */
|
|
/* ), */
|
|
/* ), */
|
|
/* ), */
|
|
),
|
|
));
|
|
//pr($entry);
|
|
|
|
if (!isset($amount))
|
|
$amount = $entry['LedgerEntry']['amount'];
|
|
|
|
$A = new Account();
|
|
|
|
$ids = $this->Ledger->Account->postLedgerEntry
|
|
(array('transaction_id' => $transaction_id),
|
|
null,
|
|
array('debit_ledger_id' => $A->currentLedgerID($entry['CreditLedger']['account_id']),
|
|
'credit_ledger_id' => $A->currentLedgerID($entry['DebitLedger']['account_id']),
|
|
'effective_date' => $entry['LedgerEntry']['effective_date'],
|
|
//'effective_date' => $entry['LedgerEntry']['effective_date'],
|
|
'amount' => $amount,
|
|
'lease_id' => $entry['Lease']['id'],
|
|
'customer_id' => $entry['Customer']['id'],
|
|
'comment' => "Reversal of Ledger Entry #{$id}",
|
|
),
|
|
array('debit' => array(array('LedgerEntry' => array('id' => $entry['LedgerEntry']['id'],
|
|
'amount' => $amount,
|
|
))),
|
|
'credit' => array(array('LedgerEntry' => array('id' => $entry['LedgerEntry']['id'],
|
|
'amount' => $amount,
|
|
))),
|
|
));
|
|
|
|
if ($ids['error'])
|
|
return null;
|
|
|
|
$tid = $ids['transaction_id'];
|
|
|
|
pr(compact('entry'));
|
|
|
|
foreach (array('Debit', 'Credit') AS $dc_type) {
|
|
foreach ($entry[$dc_type . 'ReconciliationLedgerEntry'] AS $RLE) {
|
|
pr(array('checkpoint' => "Reverse $dc_type LE",
|
|
compact('id', 'rec_id', 'RLE')));
|
|
if ($RLE['id'] == $rec_id) {
|
|
pr(array('checkpoint' => "Skipping Reverse $dc_type LE, due to rec_id",
|
|
compact('id', 'RLE')));
|
|
continue;
|
|
}
|
|
|
|
if (!$this->reverse($RLE['id'], $RLE['Reconciliation']['amount'], $tid, $id))
|
|
$ids['error'] = true;
|
|
|
|
/* $rids = $this->Ledger->Account->postLedgerEntry */
|
|
/* (array('transaction_id' => $tid), */
|
|
/* null, */
|
|
/* array('debit_ledger_id' => $A->currentLedgerID($RLE['CreditLedger']['account_id']), */
|
|
/* 'credit_ledger_id' => $A->currentLedgerID($RLE['DebitLedger']['account_id']), */
|
|
/* 'effective_date' => $RLE['effective_date'], */
|
|
/* //'effective_date' => $RLE['effective_date'], */
|
|
/* 'amount' => $RLE['Reconciliation']['amount'], */
|
|
/* 'lease_id' => $entry['Lease']['id'], */
|
|
/* 'customer_id' => $entry['Customer']['id'], */
|
|
/* 'comment' => "Reversal of Ledger Entry #{$RLE['id']}", */
|
|
/* ), */
|
|
/* array('debit' => array(array('LedgerEntry' => array('id' => $RLE['id'], */
|
|
/* 'amount' => $RLE['Reconciliation']['amount'], */
|
|
/* ))), */
|
|
/* 'credit' => array(array('LedgerEntry' => array('id' => $RLE['id'], */
|
|
/* 'amount' => $RLE['Reconciliation']['amount'], */
|
|
/* ))), */
|
|
/* )); */
|
|
|
|
/* if ($rids['error']) */
|
|
/* $ids['error'] = true; */
|
|
}
|
|
}
|
|
|
|
if ($ids['error'])
|
|
return null;
|
|
|
|
return $ids['id'];
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: reverse
|
|
* - Reverses the charges
|
|
*
|
|
* SAMPLE MOVE IN w/ PRE PAYMENT
|
|
* DEPOSIT RENT A/R RECEIPT CHECK PETTY BANK
|
|
* ------- ------- ------- ------- ------- ------- -------
|
|
* |25 | 25| | | | |
|
|
* | |20 20| | | | |
|
|
* | |20 20| | | | |
|
|
* | |20 20| | | | |
|
|
* | | |25 25| | | |
|
|
* | | |20 20| | | |
|
|
* | | |20 20| | | |
|
|
* | | |20 20| | | |
|
|
* | | | |85 85| | |
|
|
* | | | | |85 | 85|
|
|
|
|
* MOVE OUT and REFUND FINAL MONTH
|
|
* DEPOSIT RENT C/P RECEIPT CHECK PETTY BANK
|
|
* ------- ------- ------- ------- ------- ------- -------
|
|
* 25| | |25 | | | | t20 e20a
|
|
* | 20| |20 | | | | t20 e20b
|
|
|
|
* -ONE REFUND CHECK-
|
|
* | | 25| |25 | | | t30 e30a
|
|
* | | 20| |20 | | | t30 e30b
|
|
* | | | 45| | | |45 t40 e40
|
|
* -OR MULTIPLE-
|
|
* | | 15| |15 | | | t50a e50a
|
|
* | | | 15| | |15 | t60a e60a
|
|
* | | 30| |30 | | | t50b e50b
|
|
* | | | 30| | | |30 t60b e60b
|
|
* | | | | | | |
|
|
|
|
|
|
OPTION 1
|
|
* |-25 | -25| | | | |
|
|
* | |-20 -20| | | | |
|
|
* | | |-25 -25| | | |
|
|
* | | |-20 -20| | | |
|
|
|
|
OPTION 2
|
|
* |-25 | | -25| | | |
|
|
* | |-20 | -20| | | |
|
|
* | | | |-15 | -15| |
|
|
* | | | |-30 | | -30|
|
|
* | | | | | | |
|
|
*
|
|
*/
|
|
function reverse($ledger_entries, $stamp = null) {
|
|
pr(array('LedgerEntry::reverse',
|
|
compact('ledger_entries', 'stamp')));
|
|
|
|
// If the user only wants to reverse one ID, we'll allow it
|
|
if (!is_array($ledger_entries))
|
|
$ledger_entries = $this->find
|
|
('all', array
|
|
('contain' => false,
|
|
'conditions' => array('LedgerEntry.id' => $ledger_entries)));
|
|
|
|
$A = new Account();
|
|
|
|
$ar_account_id = $A->accountReceivableAccountID();
|
|
$receipt_account_id = $A->receiptAccountID();
|
|
|
|
$transaction_id = null;
|
|
foreach ($ledger_entries AS $entry) {
|
|
$entry = $entry['LedgerEntry'];
|
|
$amount = -1*$entry['amount'];
|
|
|
|
if (isset($entry['credit_account_id']))
|
|
$refund_account_id = $entry['credit_account_id'];
|
|
elseif (isset($entry['CreditLedger']['Account']['id']))
|
|
$refund_account_id = $entry['CreditLedger']['Account']['id'];
|
|
elseif (isset($entry['credit_ledger_id']))
|
|
$refund_account_id = $this->Ledger->accountID($entry['credit_ledger_id']);
|
|
else
|
|
return null;
|
|
|
|
// post new refund in the income account
|
|
$ids = $A->postLedgerEntry
|
|
(array('transaction_id' => $transaction_id),
|
|
null,
|
|
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),
|
|
'credit_ledger_id' => $A->currentLedgerID($refund_account_id),
|
|
'effective_date' => $entry['effective_date'],
|
|
'through_date' => $entry['through_date'],
|
|
'amount' => $amount,
|
|
'lease_id' => $entry['lease_id'],
|
|
'customer_id' => $entry['customer_id'],
|
|
'comment' => "Refund; Entry #{$entry['id']}",
|
|
),
|
|
array('debit' => array
|
|
(array('LedgerEntry' =>
|
|
array('id' => $entry['id'],
|
|
'amount' => $amount))),
|
|
)
|
|
);
|
|
|
|
if ($ids['error'])
|
|
return null;
|
|
$transaction_id = $ids['transaction_id'];
|
|
|
|
pr(array('checkpoint' => 'Posted Refund Ledger Entry',
|
|
compact('ids', 'amount', 'refund_account_id', 'ar_account_id')));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: stats
|
|
* - Returns summary data from the requested ledger entry
|
|
*/
|
|
function stats($id) {
|
|
|
|
$query = array
|
|
(
|
|
'fields' => array("SUM(Reconciliation.amount) AS 'reconciled'"),
|
|
|
|
'conditions' => array(isset($cond) ? $cond : array(),
|
|
array('LedgerEntry.id' => $id)),
|
|
|
|
'group' => 'LedgerEntry.id',
|
|
);
|
|
|
|
// Get the applied amounts on the debit side
|
|
$query['link'] =
|
|
array('DebitReconciliationLedgerEntry' => array('alias' => 'DRLE', 'DRLETransaction' => array('class' => 'Transaction')));
|
|
$tmpstats = $this->find('first', $query);
|
|
$stats['debit_amount_reconciled'] = $tmpstats[0]['reconciled'];
|
|
|
|
// Get the applied amounts on the credit side
|
|
$query['link'] =
|
|
array('CreditReconciliationLedgerEntry' => array('alias' => 'CRLE', 'CRLETransaction' => array('class' => 'Transaction')));
|
|
$tmpstats = $this->find('first', $query);
|
|
$stats['credit_amount_reconciled'] = $tmpstats[0]['reconciled'];
|
|
|
|
return $stats;
|
|
}
|
|
|
|
}
|