This rework is nowhere near complete, but there are certain things that are falling in place, and worth capturing. I started a branch for just this purpose of being able to check in intermediate work.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@352 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
@@ -1,13 +1,6 @@
|
||||
<?php
|
||||
class Account extends AppModel {
|
||||
|
||||
var $name = 'Account';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'name' => array('notempty'),
|
||||
'external_name' => array('notempty')
|
||||
);
|
||||
|
||||
var $hasOne = array(
|
||||
'CurrentLedger' => array(
|
||||
'className' => 'Ledger',
|
||||
@@ -327,25 +320,46 @@ class Account extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findLedgerEntries
|
||||
* function: ledgerEntries
|
||||
* - Returns an array of ledger entries that belong to the given
|
||||
* account, either just from the current ledger, or from all ledgers.
|
||||
*/
|
||||
function findLedgerEntries($id, $all = false, $cond = null, $link = null) {
|
||||
function ledgerEntries($id, $all = false, $cond = null, $link = null) {
|
||||
/* pr(array('function' => 'Account::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'all', 'cond', 'link'), */
|
||||
/* )); */
|
||||
|
||||
$entries = array();
|
||||
foreach ($this->ledgers($id, $all) AS $ledger_id) {
|
||||
$ledger_entries = $this->Ledger->findLedgerEntries
|
||||
($ledger_id, $this->type($id), $cond, $link);
|
||||
$entries = array_merge($entries, $ledger_entries);
|
||||
}
|
||||
/* $this->Entry->find */
|
||||
/* ('all', array */
|
||||
/* ('contain' => array(), */
|
||||
/* 'conditions' => array('Entry.account_id' => $id) */
|
||||
/* )); */
|
||||
|
||||
$stats = $this->stats($id, $all, $cond);
|
||||
$entries = array('Entries' => $entries,
|
||||
'summary' => $stats['Ledger']);
|
||||
$ledgers = $this->ledgers($id, $all);
|
||||
|
||||
/* $this->Ledger->DoubleEntry->find */
|
||||
/* ('all', array */
|
||||
/* ('contain' => array('Ledger'), */
|
||||
/* 'conditions' => array('OR' => */
|
||||
/* array('DoubleEntry.debit_ledger_id' => $ledgers), */
|
||||
/* array('DoubleEntry.credit_ledger_id' => $ledgers)), */
|
||||
/* 'fields' => */
|
||||
/* )); */
|
||||
|
||||
$entries = $this->Ledger->find
|
||||
('all', array
|
||||
('link' =>
|
||||
array('Account',
|
||||
'DoubleEntry' => array
|
||||
('fields' => $this->Ledger->DoubleEntry->debitCreditFields('DoubleEntry', 'Ledger', false)),
|
||||
),
|
||||
|
||||
'conditions' => array('Ledger.id' => $ledgers),
|
||||
));
|
||||
|
||||
/* $stats = $this->stats($id, $all, $cond); */
|
||||
/* $entries = array('Entries' => $entries, */
|
||||
/* 'summary' => $stats['Ledger']); */
|
||||
|
||||
/* pr(array('function' => 'Account::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'all', 'cond', 'link'), */
|
||||
@@ -379,7 +393,7 @@ class Account extends AppModel {
|
||||
foreach ($rel_ids AS $rel_id)
|
||||
$ledger_ids = array_merge($ledger_ids, $this->ledgers($rel_id));
|
||||
|
||||
array_push($cond, $this->Ledger->LedgerEntry->conditionEntryAsCreditOrDebit($ledger_ids));
|
||||
array_push($cond, $this->Ledger->DoubleEntry->conditionEntryAsCreditOrDebit($ledger_ids));
|
||||
$entries = $this->findLedgerEntries($id, $all, $cond, $link);
|
||||
|
||||
/* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */
|
||||
@@ -413,28 +427,28 @@ class Account extends AppModel {
|
||||
('link' => array
|
||||
('Ledger' => array
|
||||
('fields' => array(),
|
||||
"LedgerEntry" => array
|
||||
('class' => "{$ucfund}LedgerEntry",
|
||||
"DoubleEntry" => array
|
||||
('class' => "{$ucfund}DoubleEntry",
|
||||
'fields' => array('id', 'customer_id', 'lease_id', 'amount'),
|
||||
"ReconciliationLedgerEntry" => array
|
||||
('class' => "{$ucfund}ReconciliationLedgerEntry",
|
||||
"ReconciliationDoubleEntry" => array
|
||||
('class' => "{$ucfund}ReconciliationDoubleEntry",
|
||||
'fields' => array
|
||||
("COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'",
|
||||
"LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
|
||||
"DoubleEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'group' => ("LedgerEntry.id" .
|
||||
" HAVING LedgerEntry.amount" .
|
||||
'group' => ("DoubleEntry.id" .
|
||||
" HAVING DoubleEntry.amount" .
|
||||
" <> COALESCE(SUM(Reconciliation.amount),0)"),
|
||||
'conditions' => $cond,
|
||||
'fields' => array(),
|
||||
));
|
||||
$balance = 0;
|
||||
foreach ($unreconciled[$fund]['entry'] AS &$entry) {
|
||||
$entry = array_merge(array_diff_key($entry["LedgerEntry"], array(0=>true)),
|
||||
$entry = array_merge(array_diff_key($entry["DoubleEntry"], array(0=>true)),
|
||||
$entry[0]);
|
||||
$balance += $entry['balance'];
|
||||
}
|
||||
@@ -448,7 +462,7 @@ class Account extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconcileNewLedgerEntry
|
||||
* function: amountWouldReconcile
|
||||
* - Returns which ledger entries a new credit/debit would
|
||||
* reconcile, and how much.
|
||||
*
|
||||
@@ -459,7 +473,7 @@ class Account extends AppModel {
|
||||
* whatever algorithm is simplest.
|
||||
*/
|
||||
|
||||
function reconcileNewLedgerEntry($id, $fundamental_type, $amount, $cond = null) {
|
||||
function amountWouldReconcile($id, $fundamental_type, $amount, $cond = null) {
|
||||
$ofund = $this->fundamentalOpposite($fundamental_type);
|
||||
$unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0));
|
||||
$applied = 0;
|
||||
@@ -498,7 +512,78 @@ class Account extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: postLedgerEntry
|
||||
* function: reconcileLedgerEntries
|
||||
* - Returns which ledger entries a new credit/debit would
|
||||
* reconcile, and how much.
|
||||
*
|
||||
* - REVISIT <AP> 20090617
|
||||
* This should be subject to different algorithms, such
|
||||
* as apply to oldest charges first, newest first, to fees
|
||||
* before rent, etc. Until we get there, I'll hardcode
|
||||
* whatever algorithm is simplest.
|
||||
*/
|
||||
|
||||
function reconcileLedgerEntries($id, $cond = null) {
|
||||
$unreconciled = $this->findUnreconciledLedgerEntries($id, null, $cond);
|
||||
pr(compact('unreconciled'));
|
||||
|
||||
$entry = array();
|
||||
foreach (array('debit', 'credit') AS $dc)
|
||||
$entry[$dc] = array('balance' => 0);
|
||||
|
||||
while ($entry['debit'] && $entry['credit']) {
|
||||
|
||||
// If/When we've exhausted this/these entries, move the next
|
||||
foreach (array('debit', 'credit') AS $dc) {
|
||||
if ($entry[$dc]['balance'] <= 0) {
|
||||
$entry[$dc] =& current($unreconciled[$dc]['entry']);
|
||||
next($unreconciled[$dc]['entry']);
|
||||
$entry[$dc]['applied'] = 0;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, both entries are valid with a positive balance
|
||||
$apply = min($entry['debit']['balance'], $entry['credit']['balance']);
|
||||
|
||||
|
||||
// REVISIT <AP>: 20090716
|
||||
// NOT YET ENTERING THE RECONCILIATION SO THAT WE CAN TEST
|
||||
// MUST ADD THE RECONCILIATION ENTRY!!!!
|
||||
|
||||
foreach (array('debit', 'credit') AS $dc) {
|
||||
$entry[$dc]['applied'] += $apply;
|
||||
$entry[$dc]['reconciled'] += $apply;
|
||||
$entry[$dc]['balance'] -= $apply;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array('debit', 'credit') AS $dc) {
|
||||
$unreconciled[$dc]['applied'] = 0;
|
||||
//$unreconciled[$dc]['unapplied'] = 0;
|
||||
foreach ($unreconciled[$dc]['entry'] AS $i => $entry) {
|
||||
if (isset($entry[$dc]['applied']))
|
||||
$unreconciled[$dc]['applied'] += $entry[$dc]['applied'];
|
||||
else
|
||||
unset($unreconciled[$dc]['entry'][$i]);
|
||||
|
||||
//$unreconciled[$dc]['unapplied'] += $entry[$dc]['balance'];
|
||||
}
|
||||
$unreconciled[$dc]['balance'] -= $unreconciled[$dc]['applied'];
|
||||
}
|
||||
|
||||
$unreconciled['debit'] ['unapplied'] = $unreconciled['credit']['balance'];
|
||||
$unreconciled['credit']['unapplied'] = $unreconciled['debit'] ['balance'];
|
||||
pr(compact('unreconciled'));
|
||||
|
||||
return $unreconciled;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: postDoubleEntry
|
||||
* -
|
||||
* transaction_data
|
||||
* - transaction_id (optional... if set all else is ignored)
|
||||
@@ -513,7 +598,7 @@ class Account extends AppModel {
|
||||
* - name
|
||||
*/
|
||||
|
||||
function postLedgerEntry($transaction_data,
|
||||
function postDoubleEntry($transaction_data,
|
||||
$monetary_data,
|
||||
$entry_data,
|
||||
$reconcile = null) {
|
||||
@@ -566,7 +651,7 @@ class Account extends AppModel {
|
||||
// No distinguishing features of Cash, just
|
||||
// use the shared monetary source
|
||||
$monetary_data['monetary_source_id'] =
|
||||
$this->Ledger->LedgerEntry->MonetarySource->nameToID('Cash');
|
||||
$this->Ledger->DoubleEntry->MonetarySource->nameToID('Cash');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +719,7 @@ class Account extends AppModel {
|
||||
|
||||
//pr(array('pre-save', compact('entry_data')));
|
||||
// Create it!
|
||||
$new_entry = new LedgerEntry();
|
||||
$new_entry = new DoubleEntry();
|
||||
$new_entry->create();
|
||||
if (!$new_entry->saveAll($entry_data, array('validate'=>false))) {
|
||||
return array('error' => true);
|
||||
@@ -664,9 +749,9 @@ class Account extends AppModel {
|
||||
|
||||
if ($reconcile_set === 'receipt') {
|
||||
$C = new Customer();
|
||||
$reconciled = $C->reconcileNewLedgerEntry($entry_data['customer_id'],
|
||||
$this->fundamentalOpposite($dc_type),
|
||||
$entry_data['amount']);
|
||||
$reconciled = $C->amountWouldReconcile($entry_data['customer_id'],
|
||||
$this->fundamentalOpposite($dc_type),
|
||||
$entry_data['amount']);
|
||||
|
||||
/* pr(array("reconcile receipt", */
|
||||
/* compact('reconciled', 'split_transaction', 'transaction_data'))); */
|
||||
@@ -689,7 +774,7 @@ class Account extends AppModel {
|
||||
|
||||
// Payment must debit the Receipt ledger, and credit the A/R ledger
|
||||
// debit: Receipt credit: A/R
|
||||
$ids = $this->postLedgerEntry
|
||||
$ids = $this->postDoubleEntry
|
||||
($split_transaction,
|
||||
null,
|
||||
array('debit_ledger_id' => $this->currentLedgerID($this->receiptAccountID()),
|
||||
@@ -698,9 +783,9 @@ class Account extends AppModel {
|
||||
'lease_id' => $rec['lease_id'],
|
||||
'customer_id' => $rec['customer_id'],
|
||||
),
|
||||
array('debit' => array(array('LedgerEntry' => array('id' => $new_entry->id,
|
||||
array('debit' => array(array('DoubleEntry' => array('id' => $new_entry->id,
|
||||
'amount' => $rec['applied']))),
|
||||
'credit' => array(array('LedgerEntry' => array('id' => $rec['id'],
|
||||
'credit' => array(array('DoubleEntry' => array('id' => $rec['id'],
|
||||
'amount' => $rec['applied']))))
|
||||
);
|
||||
// Keep using the same split transaction for all reconciled entries
|
||||
@@ -717,19 +802,19 @@ class Account extends AppModel {
|
||||
if (is_array($reconcile_set)) {
|
||||
//pr("reconcile_set is array");
|
||||
foreach ($reconcile_set AS $reconcile_entry) {
|
||||
if (!isset($reconcile_entry['LedgerEntry']['id']))
|
||||
if (!isset($reconcile_entry['DoubleEntry']['id']))
|
||||
continue;
|
||||
|
||||
$amount = $reconcile_entry['LedgerEntry']['amount'];
|
||||
$amount = $reconcile_entry['DoubleEntry']['amount'];
|
||||
if (!$amount)
|
||||
continue;
|
||||
|
||||
if ($dc_type == 'debit') {
|
||||
$debit_ledger_entry_id = $new_entry->id;
|
||||
$credit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
|
||||
$credit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
|
||||
}
|
||||
else {
|
||||
$debit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
|
||||
$debit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
|
||||
$credit_ledger_entry_id = $new_entry->id;
|
||||
}
|
||||
|
||||
@@ -749,9 +834,9 @@ class Account extends AppModel {
|
||||
|
||||
$ret = array
|
||||
('error' => $err,
|
||||
'id' => $new_entry->data['LedgerEntry']['id'],
|
||||
'transaction_id' => $new_entry->data['LedgerEntry']['transaction_id'],
|
||||
'monetary_source_id' => $new_entry->data['LedgerEntry']['monetary_source_id']);
|
||||
'id' => $new_entry->data['DoubleEntry']['id'],
|
||||
'transaction_id' => $new_entry->data['DoubleEntry']['transaction_id'],
|
||||
'monetary_source_id' => $new_entry->data['DoubleEntry']['monetary_source_id']);
|
||||
|
||||
if (isset($split_transaction['transaction_id']))
|
||||
$ret['split_transaction_id'] = $split_transaction['transaction_id'];
|
||||
@@ -783,7 +868,7 @@ class Account extends AppModel {
|
||||
if ($ledger['total'] == 0)
|
||||
continue;
|
||||
|
||||
$ids = $this->postLedgerEntry
|
||||
$ids = $this->postDoubleEntry
|
||||
($transaction,
|
||||
null,
|
||||
array('debit_account_id' => $deposit_account_id,
|
||||
@@ -795,7 +880,7 @@ class Account extends AppModel {
|
||||
//pr(compact('ids'));
|
||||
|
||||
if ($ids['error'])
|
||||
die("closeAndDeposit : postLedgerEntry returned error!");
|
||||
die("closeAndDeposit : postDoubleEntry returned error!");
|
||||
|
||||
$transaction = array_intersect_key($ids, array('transaction_id'=>1));
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class LinkableBehavior extends ModelBehavior {
|
||||
protected $_defaults = array('type' => 'LEFT');
|
||||
|
||||
function pr($lev, $mixed) {
|
||||
if ($lev >= 5)
|
||||
if ($lev >= 3)
|
||||
return;
|
||||
|
||||
pr($mixed);
|
||||
@@ -159,7 +159,7 @@ class LinkableBehavior extends ModelBehavior {
|
||||
$iterations = Set::normalize($iterator);
|
||||
$this->pr(25,
|
||||
array('checkpoint' => 'Iterations',
|
||||
compact('iterations'),
|
||||
compact('cont', 'iterator', 'iterations'),
|
||||
));
|
||||
foreach ($iterations as $alias => $options) {
|
||||
if (is_null($options)) {
|
||||
@@ -246,15 +246,32 @@ class LinkableBehavior extends ModelBehavior {
|
||||
else
|
||||
$associationAlias = $modelAlias;
|
||||
|
||||
// A couple exceptions before performing a union of
|
||||
// options and association. Namely, most fields result
|
||||
// in either/or, but a couple should include BOTH the
|
||||
// options AND the association settings.
|
||||
foreach (array('fields', 'conditions') AS $fld) {
|
||||
if (isset($options[$fld]) && is_array($options[$fld]) &&
|
||||
isset($association[$fld]) && is_array($association[$fld]))
|
||||
$options[$fld] = array_merge($options[$fld],
|
||||
$association[$fld]);
|
||||
}
|
||||
|
||||
// For any option that's not already set, use
|
||||
// whatever is specified by the assocation.
|
||||
$options += array_intersect_key($association, $this->_options);
|
||||
|
||||
// Replace all instances of the MODEL_ALIAS variable
|
||||
// tag with the correct model alias.
|
||||
$this->recursive_array_replace("%{MODEL_ALIAS}",
|
||||
$associationAlias,
|
||||
$association['conditions']);
|
||||
$options['conditions']);
|
||||
|
||||
$this->pr(15,
|
||||
array('checkpoint' => 'Models Established - Check Associations',
|
||||
'primaryModel' => $primaryAlias .' : '. $primaryModel->name,
|
||||
'foreignModel' => $foreignAlias .' : '. $foreignModel->name,
|
||||
compact('type', 'association'),
|
||||
compact('type', 'association', 'options'),
|
||||
));
|
||||
|
||||
if ($type === 'hasAndBelongsToMany') {
|
||||
@@ -270,16 +287,29 @@ class LinkableBehavior extends ModelBehavior {
|
||||
else
|
||||
$linkAlias = $Link->alias;
|
||||
|
||||
// foreignKey and associationForeignKey can refer to either
|
||||
// the model or the reference, depending on which class
|
||||
// actually defines the association. Make sure to we're
|
||||
// using the foreign keys to point to the right class.
|
||||
if ($associatedThroughReference) {
|
||||
$modelAFK = 'associationForeignKey';
|
||||
$referenceAFK = 'foreignKey';
|
||||
} else {
|
||||
$modelAFK = 'foreignKey';
|
||||
$referenceAFK = 'associationForeignKey';
|
||||
}
|
||||
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking HABTM',
|
||||
compact('linkClass', 'linkAlias'),
|
||||
compact('linkClass', 'linkAlias',
|
||||
'modelAFK', 'referenceAFK'),
|
||||
));
|
||||
|
||||
// Get the foreign key fields (for the link table) directly from
|
||||
// the defined model associations, if they exists. This is the
|
||||
// users direct specification, and therefore definitive if present.
|
||||
$modelLink = $Link->escapeField($association['foreignKey'], $linkAlias);
|
||||
$referenceLink = $Link->escapeField($association['associationForeignKey'], $linkAlias);
|
||||
$modelLink = $Link->escapeField($association[$modelAFK], $linkAlias);
|
||||
$referenceLink = $Link->escapeField($association[$referenceAFK], $linkAlias);
|
||||
|
||||
// If we haven't figured out the foreign keys, see if there is a
|
||||
// model for the link table, and if it has the appropriate
|
||||
@@ -300,6 +330,12 @@ class LinkableBehavior extends ModelBehavior {
|
||||
$referenceKey = $Reference->escapeField(null, $referenceAlias);
|
||||
$modelKey = $_Model->escapeField(null, $modelAlias);
|
||||
|
||||
$this->pr(21,
|
||||
array('checkpoint' => 'HABTM links/keys',
|
||||
array(compact('modelLink', 'modelKey'),
|
||||
compact('referenceLink', 'referenceKey')),
|
||||
));
|
||||
|
||||
// Join the linkage table to our model. We'll use an inner join,
|
||||
// as the whole purpose of the linkage table is to make this
|
||||
// connection. As we are embedding this join, the INNER will not
|
||||
@@ -345,20 +381,6 @@ class LinkableBehavior extends ModelBehavior {
|
||||
|
||||
$this->pr(19,
|
||||
array('checkpoint' => 'Conditions',
|
||||
array('options[conditions]' => $options['conditions'],
|
||||
'association[conditions]' => $association['conditions'],
|
||||
),
|
||||
));
|
||||
|
||||
// The user may have specified conditions directly in the model
|
||||
// for this join. Make sure to adhere to those conditions.
|
||||
if (isset($association['conditions']) && is_array($association['conditions']))
|
||||
$options['conditions'] = array_merge($options['conditions'], $association['conditions']);
|
||||
elseif (!empty($association['conditions']))
|
||||
$options['conditions'][] = $association['conditions'];
|
||||
|
||||
$this->pr(19,
|
||||
array('checkpoint' => 'Conditions2',
|
||||
array('options[conditions]' => $options['conditions'],
|
||||
),
|
||||
));
|
||||
@@ -372,13 +394,16 @@ class LinkableBehavior extends ModelBehavior {
|
||||
elseif (!empty($options['fields']))
|
||||
$options['fields'] = $db->fields($_Model, $modelAlias, $options['fields']);
|
||||
|
||||
$query['fields'] = array_merge($query['fields'], $options['fields'],
|
||||
(empty($association['fields'])
|
||||
? array() : $db->fields($_Model, $modelAlias, $association['fields'])));
|
||||
$query['fields'] = array_merge($query['fields'], $options['fields']);
|
||||
|
||||
$options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys));
|
||||
$options = array_intersect_key($options, $optionsKeys);
|
||||
if (!empty($options[$this->_key])) {
|
||||
$this->pr(24,
|
||||
array('checkpoint' => 'Add new iterator',
|
||||
'options[this->_key]' => $options[$this->_key],
|
||||
compact('defaults', 'modelClass', 'modelAlias'),
|
||||
));
|
||||
$iterators[] = $options[$this->_key] +
|
||||
array('defaults' =>
|
||||
array_merge($defaults,
|
||||
|
||||
@@ -19,17 +19,14 @@ class Customer extends AppModel {
|
||||
'conditions' => 'CurrentLease.close_date IS NULL',
|
||||
),
|
||||
'Lease',
|
||||
'LedgerEntry',
|
||||
'DoubleEntry',
|
||||
'ContactsCustomer',
|
||||
|
||||
'Transaction',
|
||||
);
|
||||
|
||||
var $hasAndBelongsToMany = array(
|
||||
'Contact',
|
||||
'Transaction' => array(
|
||||
'joinTable' => 'ledger_entries',
|
||||
'foreignKey' => 'customer_id',
|
||||
'associationForeignKey' => 'transaction_id',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -87,7 +84,7 @@ class Customer extends AppModel {
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries
|
||||
($A->securityDepositAccountID(),
|
||||
true, array('LedgerEntry.customer_id' => $id), $link);
|
||||
true, array('DoubleEntry.customer_id' => $id), $link);
|
||||
|
||||
/* pr(array('function' => 'Customer::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
@@ -112,7 +109,7 @@ class Customer extends AppModel {
|
||||
$unreconciled = $A->findUnreconciledLedgerEntries
|
||||
($A->accountReceivableAccountID(),
|
||||
$fundamental_type,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
array('DoubleEntry.customer_id' => $id));
|
||||
|
||||
return $unreconciled;
|
||||
}
|
||||
@@ -121,7 +118,7 @@ class Customer extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconcileNewLedgerEntry
|
||||
* function: amountWouldReconcile
|
||||
* - Returns which ledger entries a new credit/debit would
|
||||
* reconcile, and how much.
|
||||
*
|
||||
@@ -132,18 +129,41 @@ class Customer extends AppModel {
|
||||
* whatever algorithm is simplest.
|
||||
*/
|
||||
|
||||
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
|
||||
function amountWouldReconcile($id, $fundamental_type, $amount) {
|
||||
$A = new Account();
|
||||
$reconciled = $A->reconcileNewLedgerEntry
|
||||
$reconciled = $A->amountWouldReconcile
|
||||
($A->accountReceivableAccountID(),
|
||||
$fundamental_type,
|
||||
$amount,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
array('DoubleEntry.customer_id' => $id));
|
||||
|
||||
return $reconciled;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconcileLedgerEntries
|
||||
* - Returns which ledger entries a new credit/debit would
|
||||
* reconcile, and how much.
|
||||
*
|
||||
* - REVISIT <AP> 20090617
|
||||
* This should be subject to different algorithms, such
|
||||
* as apply to oldest charges first, newest first, to fees
|
||||
* before rent, etc. Until we get there, I'll hardcode
|
||||
* whatever algorithm is simplest.
|
||||
*/
|
||||
|
||||
function reconcileLedgerEntries($id) {
|
||||
$A = new Account();
|
||||
$reconciled = $A->reconcileLedgerEntries
|
||||
($A->accountReceivableAccountID(),
|
||||
array('DoubleEntry.customer_id' => $id));
|
||||
|
||||
return $reconciled;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -274,7 +294,7 @@ class Customer extends AppModel {
|
||||
|
||||
$A = new Account();
|
||||
$stats = $A->stats($A->accountReceivableAccountID(), true,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
array('DoubleEntry.customer_id' => $id));
|
||||
|
||||
// Pull to the top level and return
|
||||
$stats = $stats['Ledger'];
|
||||
|
||||
259
site/models/double_entry.php
Normal file
259
site/models/double_entry.php
Normal file
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
class DoubleEntry extends AppModel {
|
||||
|
||||
var $name = 'DoubleEntry';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'transaction_id' => array('numeric'),
|
||||
'amount' => array('money')
|
||||
);
|
||||
|
||||
var $hasOne = array(
|
||||
'DebitEntry' => array(
|
||||
'className' => 'Entry',
|
||||
'conditions' => array('DebitEntry.crdr' => 'DEBIT'),
|
||||
),
|
||||
'CreditEntry' => array(
|
||||
'className' => 'Entry',
|
||||
'conditions' => array('CreditEntry.crdr' => 'CREDIT'),
|
||||
),
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'Entry',
|
||||
);
|
||||
|
||||
|
||||
var $belongsTo = array(
|
||||
'Transaction',
|
||||
'Invoice' => array(
|
||||
'className' => 'Transaction',
|
||||
'conditions' => array('Invoice.type' => 'INVOICE'),
|
||||
),
|
||||
'Receipt' => array(
|
||||
'className' => 'Transaction',
|
||||
'conditions' => array('Invoice.type' => 'RECEIPT'),
|
||||
),
|
||||
|
||||
|
||||
'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' => ''
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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 debitCreditFields($double_name = 'DoubleEntry', $ledger_name = 'Ledger', $sum = true) {
|
||||
$fields = array
|
||||
(
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$double_name}.debit_ledger_id = {$ledger_name}.id,
|
||||
{$double_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS debit' . ($sum ? 's' : ''),
|
||||
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$double_name}.credit_ledger_id = {$ledger_name}.id,
|
||||
{$double_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS credit' . ($sum ? 's' : ''),
|
||||
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF(Account.type IN ('ASSET', 'EXPENSE'),
|
||||
IF({$double_name}.debit_ledger_id = {$ledger_name}.id, 1, -1),
|
||||
IF({$double_name}.credit_ledger_id = {$ledger_name}.id, 1, -1)
|
||||
) * IF({$double_name}.amount, {$double_name}.amount, 0)" .
|
||||
($sum ? ')' : '') . ' AS balance',
|
||||
);
|
||||
|
||||
if ($sum)
|
||||
$fields[] = "COUNT({$double_name}.id) AS entries";
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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',
|
||||
'lease_id', 'customer_id', 'comment', 'amount');
|
||||
|
||||
if (isset($ledger_id)) {
|
||||
$fields[] = ("IF(DoubleEntry.debit_ledger_id = $ledger_id," .
|
||||
" DoubleEntry.amount, NULL) AS debit");
|
||||
$fields[] = ("IF(DoubleEntry.credit_ledger_id = $ledger_id," .
|
||||
" DoubleEntry.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(DoubleEntry.{$ledger_type}_ledger_id = $ledger_id," .
|
||||
" 1, -1) * DoubleEntry.amount) AS balance");
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
function ledgerContextFields2($ledger_id = null, $account_id = null, $account_type = null) {
|
||||
$fields = array('id', 'effective_date', 'comment', 'amount');
|
||||
|
||||
if (isset($ledger_id)) {
|
||||
$fields[] = ("IF(DoubleEntry.debit_ledger_id = $ledger_id," .
|
||||
" SUM(DoubleEntry.amount), NULL) AS debit");
|
||||
$fields[] = ("IF(DoubleEntry.credit_ledger_id = $ledger_id," .
|
||||
" SUM(DoubleEntry.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(DoubleEntry.{$account_ftype}_ledger_id = $ledger_id," .
|
||||
" 1, -1) * SUM(DoubleEntry.amount)) AS balance");
|
||||
}
|
||||
}
|
||||
elseif (isset($account_id)) {
|
||||
$fields[] = ("IF(DebitLedger.account_id = $account_id," .
|
||||
" SUM(DoubleEntry.amount), NULL) AS debit");
|
||||
$fields[] = ("IF(CreditLedger.account_id = $account_id," .
|
||||
" SUM(DoubleEntry.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(DoubleEntry.amount)) AS balance");
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
function ledgerContextConditions($ledger_id, $account_type) {
|
||||
if (isset($ledger_id)) {
|
||||
return array
|
||||
('OR' =>
|
||||
array(array('DoubleEntry.debit_ledger_id' => $ledger_id),
|
||||
array('DoubleEntry.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 double_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: stats
|
||||
* - Returns summary data from the requested double entry
|
||||
*/
|
||||
function stats($id) {
|
||||
|
||||
/* $query = array */
|
||||
/* ( */
|
||||
/* 'fields' => array("SUM(Reconciliation.amount) AS 'reconciled'"), */
|
||||
|
||||
/* 'conditions' => array(isset($cond) ? $cond : array(), */
|
||||
/* array('DoubleEntry.id' => $id)), */
|
||||
|
||||
/* 'group' => 'DoubleEntry.id', */
|
||||
/* ); */
|
||||
|
||||
/* // Get the applied amounts on the debit side */
|
||||
/* $query['link'] = */
|
||||
/* array('DebitReconciliationDoubleEntry' => 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('CreditReconciliationDoubleEntry' => array('alias' => 'CRLE', 'CRLETransaction' => array('class' => 'Transaction'))); */
|
||||
/* $tmpstats = $this->find('first', $query); */
|
||||
/* $stats['credit_amount_reconciled'] = $tmpstats[0]['reconciled']; */
|
||||
|
||||
$stats['debit']['amount_reconciled'] = 0;
|
||||
$stats['credit']['amount_reconciled'] = 0;
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
}
|
||||
578
site/models/entry.php
Normal file
578
site/models/entry.php
Normal file
@@ -0,0 +1,578 @@
|
||||
<?php
|
||||
class Entry extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'Account',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
var $hasAndBelongsToMany = array(
|
||||
|
||||
// The Payments that match THIS Charge (if it is one)
|
||||
'Payment' => array(
|
||||
'className' => 'Entry',
|
||||
'joinTable' => 'charges_payments',
|
||||
'linkalias' => 'AppliedPayment',
|
||||
'foreignKey' => 'charge_entry_id',
|
||||
'associationForeignKey' => 'payment_entry_id',
|
||||
),
|
||||
|
||||
// The Charges that match THIS Payment (if it is one)
|
||||
'Charge' => array(
|
||||
'className' => 'Entry',
|
||||
'joinTable' => 'charges_payments',
|
||||
'linkalias' => 'AppliedCharge',
|
||||
'foreignKey' => 'payment_entry_id',
|
||||
'associationForeignKey' => 'charge_entry_id',
|
||||
),
|
||||
|
||||
// The Debits of this same account matching THIS Credit (if it is one)
|
||||
'Debit' => array(
|
||||
'className' => 'Entry',
|
||||
'joinTable' => 'reconciliations',
|
||||
'linkalias' => 'AppliedDebit',
|
||||
'foreignKey' => 'credit_entry_id',
|
||||
'associationForeignKey' => 'debit_entry_id',
|
||||
),
|
||||
|
||||
// The Credits of this same account matching THIS Debit (if it is one)
|
||||
'Credit' => array(
|
||||
'className' => 'Entry',
|
||||
'joinTable' => 'reconciliations',
|
||||
'linkalias' => 'AppliedCredit',
|
||||
'foreignKey' => 'debit_entry_id',
|
||||
'associationForeignKey' => 'credit_entry_id',
|
||||
),
|
||||
|
||||
/* // The Entries of this same account matching THIS Entry */
|
||||
/* 'REntry' => array( */
|
||||
/* 'className' => 'Entry', */
|
||||
/* 'joinTable' => 'reconciliations', */
|
||||
/* //'linkalias' => 'AppliedCredit', */
|
||||
/* 'foreignKey' => false, */
|
||||
/* 'associationForeignKey' => false, */
|
||||
/* 'conditions' => array( */
|
||||
/* "Reconciliation.credit_entry_id */
|
||||
/* = IF(Entry.crdr = 'CREDIT', Entry.id, REntry.id)", */
|
||||
/* "Reconciliation.debit_entry_id */
|
||||
/* = IF(Entry.crdr = 'DEBIT', Entry.id, REntry.id)" */
|
||||
/* ), */
|
||||
/* ), */
|
||||
);
|
||||
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addCharge
|
||||
* - Adds a new charge
|
||||
*/
|
||||
|
||||
function addCharge($data, $transaction, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key charge parameters
|
||||
$charge = array_intersect_key($data, array('stamp'=>1, 'amount'=>1, 'account_id'=>1));
|
||||
|
||||
$charge['customer_id'] = $customer_id;
|
||||
$charge['lease_id'] = $lease_id;
|
||||
$charge['type'] = 'CHARGE';
|
||||
|
||||
$ids = $this->Entry->Ledger->Account->postDoubleEntry
|
||||
($transaction,
|
||||
$charge,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
|
||||
) + $data
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
return $ids['charge_id'];
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addPayment
|
||||
* - Adds a new payment
|
||||
*/
|
||||
|
||||
function addPayment($data, $transaction, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key payment parameters
|
||||
$payment = array_intersect_key($data, array('stamp'=>1, 'name'=>1, 'monetary_type'=>1,
|
||||
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1,
|
||||
'amount'=>1, 'account_id'=>1));
|
||||
$payment['customer_id'] = $customer_id;
|
||||
$payment['type'] = 'PAYMENT';
|
||||
|
||||
$ids = $this->Entry->Ledger->Account->postDoubleEntry
|
||||
($transaction,
|
||||
$payment,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
|
||||
) + $data
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
return $ids['payment_id'];
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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: 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('Entry::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('Entry.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['Entry'];
|
||||
$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->postEntry
|
||||
(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('Entry' =>
|
||||
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: reconcileConditions
|
||||
* - Returns entries which reconcile, or match, the set of entries
|
||||
* requested through the $cond argument.
|
||||
*/
|
||||
|
||||
function reconcilingQuery($double_name = 'DoubleEntry', $sum = false) {
|
||||
$applied = array();
|
||||
foreach (array('Payment', 'Charge') AS $pc) {
|
||||
$applied[$pc] = "COALESCE(Applied{$pc}.amount,0)";
|
||||
if ($sum)
|
||||
$applied[$pc] = "SUM({$applied[$pc]})";
|
||||
}
|
||||
|
||||
return array
|
||||
('fields' => array("IF(Entry.type = 'CHARGE'," .
|
||||
" {$applied['Payment']}, {$applied['Charge']}) AS 'applied'",
|
||||
"{$double_name}.amount - IF(Entry.type = 'CHARGE'," .
|
||||
" {$applied['Payment']}, {$applied['Charge']}) AS 'balance'",
|
||||
),
|
||||
'conditions' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: matchingEntries
|
||||
* - Returns entries which reconcile, or match, the set of entries
|
||||
* requested through the $cond argument.
|
||||
*/
|
||||
|
||||
function matchingEntries($cond, $group, $strip_rec = false, $strip_unrec = false) {
|
||||
|
||||
$rquery = $this->reconcilingQuery('DoubleEntry', $group);
|
||||
$fields = array_merge(array('Entry.*'), $rquery['fields']);
|
||||
$cond = array_merge($cond, $rquery['conditions']);
|
||||
|
||||
$reconciled = $this->find
|
||||
('all', array
|
||||
('link' => array('DoubleEntry', 'Account',
|
||||
'Charge' => array('fields' => array('Charge.*', 'AppliedCharge.*')),
|
||||
'Payment' => array('fields' => array('Payment.*', 'AppliedPayment.*')),
|
||||
),
|
||||
'fields' => $fields,
|
||||
'group' => $group,
|
||||
'conditions' => $cond,
|
||||
));
|
||||
|
||||
foreach ($reconciled AS $i => &$entry) {
|
||||
$entry['Entry'] += $entry[0];
|
||||
unset($entry[0]);
|
||||
|
||||
// Since HAVING isn't a builtin feature of CakePHP,
|
||||
// we'll have to post-process to get the desired entries
|
||||
if ($entry['Entry']['balance'] == 0) {
|
||||
if ($strip_rec)
|
||||
unset($reconciled[$i]);
|
||||
}
|
||||
else {
|
||||
if ($strip_unrec)
|
||||
unset($reconciled[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
//pr(compact('reconciled'));
|
||||
return $reconciled;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconciledEntries
|
||||
* - Returns the list of entries that have been reconciled (if $rec),
|
||||
* or not fully reconciled (if $rec is false), along with the amount
|
||||
* that has already been applied towards the entry as well as the
|
||||
* remaining balance.
|
||||
*/
|
||||
|
||||
function reconciledEntries1($id, $rec = true, $cond = null) {
|
||||
$cond[] = array('Entry.id' => $id);
|
||||
//return $this->matchingEntries($cond, array('Entry.id'), !$rec, $rec);
|
||||
return $this->matchingEntries($cond, array('Entry.id'), false, false);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconcilingEntries
|
||||
* - Returns a list of entries that reconcile against the given entry.
|
||||
* (such as payments towards a charge).
|
||||
*/
|
||||
|
||||
function reconciledEntries($id, $cond = null) {
|
||||
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
|
||||
$cond[] = array('Entry.id' => $id);
|
||||
|
||||
$entry = $this->find('first', array('recursive' => -1));
|
||||
$entry = $entry['Entry'];
|
||||
|
||||
$contain = array();
|
||||
if ($entry['type'] == 'CHARGE')
|
||||
$contain['Payment'] = array('fields' => array('Payment.*', 'ChargesPayment.amount'));
|
||||
if ($entry['type'] == 'PAYMENT')
|
||||
$contain['Charge'] = array('fields' => array('Charge.*', 'ChargesPayment.amount'));
|
||||
if ($entry['crdr'] == 'DEBIT')
|
||||
$contain['Credit'] = array('fields' => array('Credit.*', 'Reconciliation.amount'));
|
||||
if ($entry['crdr'] == 'CREDIT')
|
||||
$contain['Debit'] = array('fields' => array('Debit.*', 'Reconciliation.amount'));
|
||||
|
||||
//pr(array('reconciledEntries', compact('entry', 'contain')));
|
||||
$result = array();
|
||||
$result['entries'] = $this->find
|
||||
('first', array
|
||||
(
|
||||
'contain' => $contain,
|
||||
|
||||
/* 'zcontain' => array(//'DoubleEntry' => array('fields' => array('amount')), */
|
||||
/* 'REntry' => array('fields' => array('Reconciliation.amount'), */
|
||||
/* 'DoubleEntry'), */
|
||||
/* ), */
|
||||
|
||||
/* 'zlink' => array(//'DoubleEntry' => array('fields' => array('amount')), */
|
||||
/* 'REntry' => array('fields' => array('REntry.*', 'Reconciliation.amount'), */
|
||||
/* 'DoubleEntry'), */
|
||||
/* ), */
|
||||
|
||||
/* 'zlink' => array(//'DoubleEntry' => array('fields' => array('amount')), */
|
||||
/* 'Debit' => array('fields' => array('AppliedDebit.amount'), */
|
||||
/* 'DoubleEntry' => array('alias' => 'DebitDoubleEntry')), */
|
||||
/* 'Credit' => array('fields' => array('AppliedCredit.amount'), */
|
||||
/* 'DoubleEntry' => array('alias' => 'CreditDoubleEntry')), */
|
||||
/* ), */
|
||||
|
||||
'fields' => array('id'),
|
||||
'conditions' => $cond,
|
||||
));
|
||||
unset($result['entries']['Entry']);
|
||||
|
||||
$result['stats'] = $this->stats($id);
|
||||
return $result;
|
||||
|
||||
/* $balance = $this->find */
|
||||
/* ('first', array */
|
||||
/* ('link' => array */
|
||||
/* ( */
|
||||
/* "Charge" => array */
|
||||
/* ('fields' => array("SUM(COALESCE(ChargesPayment.amount,0)) AS 'reconciling_charges'")), */
|
||||
/* "Payment" => array */
|
||||
/* ('fields' => array("SUM(COALESCE(ChargesPayment.amount,0)) AS 'reconciling_payments'")), */
|
||||
/* ), */
|
||||
|
||||
/* 'fields' => array('Entry.amount'), */
|
||||
/* 'group' => array('Entry.id'); */
|
||||
/* 'conditions' => $cond, */
|
||||
/* )); */
|
||||
|
||||
/* pr(compact('balance')); */
|
||||
|
||||
// pull up, since there should only be one
|
||||
//$reconciling = $reconciling[0];
|
||||
|
||||
// Add the calculated fields to Entry
|
||||
|
||||
/* $reconciling['applied'] = 0; */
|
||||
/* $reconciling['balance'] = $reconciling[0]['Entry']['amount']; */
|
||||
/* foreach ($reconciling AS $i => $entry) { */
|
||||
/* $reconciling['applied'] += $entry[0]['applied']; */
|
||||
/* unset($reconciling[$i]['Entry']); */
|
||||
/* } */
|
||||
/* $reconciling['balance'] -= $reconciling['applied']; */
|
||||
|
||||
/* pr(compact('reconciling')); */
|
||||
/* return $reconciling; */
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: stats
|
||||
* - Returns summary data from the requested ledger entry
|
||||
*/
|
||||
function stats($id, $cond = null) {
|
||||
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
|
||||
$cond[] = array('Entry.id' => $id);
|
||||
|
||||
$entry = $this->find('first', array('contain' => array('DoubleEntry.amount'),
|
||||
'fields' => array('Entry.type', 'Entry.crdr')));
|
||||
$entry['Entry'] += $entry['DoubleEntry'];
|
||||
$entry = $entry['Entry'];
|
||||
|
||||
$stats = array();
|
||||
foreach(array('charge', 'payment', 'debit', 'credit') AS $rtype) {
|
||||
$Rtype = ucfirst($rtype);
|
||||
|
||||
if (($rtype == 'charge' && $entry['type'] == 'PAYMENT') ||
|
||||
($rtype == 'payment' && $entry['type'] == 'CHARGE') ||
|
||||
($rtype == 'debit' && $entry['crdr'] == 'CREDIT') ||
|
||||
($rtype == 'credit' && $entry['crdr'] == 'DEBIT')) {
|
||||
$result = $this->find
|
||||
('first', array
|
||||
('link' =>
|
||||
array($Rtype =>
|
||||
array('fields' => array("SUM(Applied{$Rtype}.amount) AS applied"))),
|
||||
'fields' => array(),
|
||||
'conditions' => $cond,
|
||||
'group' => 'Entry.id',
|
||||
));
|
||||
//pr(compact('Rtype', 'result'));
|
||||
|
||||
$sumfld = $Rtype;
|
||||
$stats[$sumfld] = $result[0];
|
||||
if (!isset($stats[$sumfld]['applied']))
|
||||
$stats[$sumfld]['applied'] = 0;
|
||||
$stats[$sumfld]['unapplied'] = $entry['amount'] - $stats[$sumfld]['applied'];
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
|
||||
/* $result = $this->find */
|
||||
/* ('first', array */
|
||||
/* ('link' => array('DoubleEntry' => array('fields' => array('amount')), */
|
||||
/* 'Credit' => array('fields' => array()), */
|
||||
/* 'Debit' => array('fields' => array())), */
|
||||
|
||||
/* 'fields' => array('Entry.crdr', */
|
||||
/* "SUM(AppliedDebit.amount) AS 'applied_debits'", */
|
||||
/* "SUM(AppliedCredit.amount) AS 'applied_credits'"), */
|
||||
|
||||
/* 'conditions' => $cond, */
|
||||
/* 'group' => 'Entry.id', */
|
||||
/* )); */
|
||||
|
||||
//pr(compact('result'));
|
||||
|
||||
$stats = array();
|
||||
if ($result['Entry']['crdr'] == 'DEBIT') {
|
||||
if ($result[0]['applied_debits'])
|
||||
die('INCONSISTENT DATABASE ENTRIES');
|
||||
$stats['applied'] = $result[0]['applied_credits'];
|
||||
}
|
||||
elseif ($result['Entry']['crdr'] == 'CREDIT') {
|
||||
if ($result[0]['applied_credits'])
|
||||
die('INCONSISTENT DATABASE ENTRIES');
|
||||
$stats['applied'] = $result[0]['applied_debits'];
|
||||
}
|
||||
|
||||
if (!isset($stats['applied']))
|
||||
$stats['applied'] = 0;
|
||||
$stats['unapplied'] = $result['DoubleEntry']['amount'] - $stats['applied'];
|
||||
|
||||
|
||||
/* $reconciled = $this->find */
|
||||
/* ('all', array */
|
||||
/* ('link' => array */
|
||||
/* ( */
|
||||
/* "Charge" => array */
|
||||
/* ('fields' => array */
|
||||
/* ('id', */
|
||||
/* "COALESCE(SUM(ChargesPayment.amount),0) AS 'reconciled'", */
|
||||
/* "Entry.amount - COALESCE(SUM(ChargesPayment.amount),0) AS 'balance'", */
|
||||
/* ), */
|
||||
/* ), */
|
||||
|
||||
/* "Payment" => array */
|
||||
/* ('fields' => array */
|
||||
/* ('id', */
|
||||
/* "COALESCE(SUM(ChargesPayment.amount),0) AS 'reconciled'", */
|
||||
/* "Entry.amount - COALESCE(SUM(ChargesPayment.amount),0) AS 'balance'", */
|
||||
/* ), */
|
||||
/* ), */
|
||||
/* ), */
|
||||
|
||||
/* 'conditions' => array(isset($cond) ? $cond : array(), */
|
||||
/* array('Entry.id' => $id)), */
|
||||
|
||||
/* 'group' => 'Entry.id', */
|
||||
/* )); */
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,6 @@
|
||||
<?php
|
||||
class Lease extends AppModel {
|
||||
|
||||
var $name = 'Lease';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'number' => array('alphanumeric'),
|
||||
'lease_type_id' => array('numeric'),
|
||||
'unit_id' => array('numeric'),
|
||||
'late_schedule_id' => array('numeric'),
|
||||
'lease_date' => array('date'),
|
||||
'movein_planned_date' => array('date'),
|
||||
'movein_date' => array('date'),
|
||||
'moveout_date' => array('date'),
|
||||
'moveout_planned_date' => array('date'),
|
||||
'notice_given_date' => array('date'),
|
||||
'notice_received_date' => array('date'),
|
||||
'close_date' => array('date'),
|
||||
'deposit' => array('money'),
|
||||
'rent' => array('money'),
|
||||
'next_rent' => array('money'),
|
||||
'next_rent_date' => array('date')
|
||||
);
|
||||
|
||||
var $belongsTo = array(
|
||||
'LeaseType',
|
||||
'Unit',
|
||||
@@ -30,7 +9,7 @@ class Lease extends AppModel {
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
|
||||
@@ -60,7 +39,7 @@ class Lease extends AppModel {
|
||||
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
$cond[] = array('LedgerEntry.lease_id' => $id);
|
||||
$cond[] = array('DoubleEntry.lease_id' => $id);
|
||||
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries($this->accountId($id),
|
||||
@@ -89,7 +68,7 @@ class Lease extends AppModel {
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries
|
||||
($A->securityDepositAccountID(),
|
||||
true, array('LedgerEntry.lease_id' => $id), $link);
|
||||
true, array('DoubleEntry.lease_id' => $id), $link);
|
||||
|
||||
/* pr(array('function' => 'Lease::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
@@ -100,42 +79,6 @@ class Lease extends AppModel {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findUnreconciledLedgerEntries
|
||||
* - Returns ledger entries that are not yet reconciled
|
||||
* (such as charges not paid).
|
||||
*/
|
||||
|
||||
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
|
||||
$A = new Account();
|
||||
return $A->findUnreconciledLedgerEntries
|
||||
($this->accountId($id), $fundamental_type, array('LedgerEntry.lease_id' => $id));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconcileNewLedgerEntry
|
||||
* - Returns which ledger entries a new credit/debit would
|
||||
* reconcile, and how much.
|
||||
*
|
||||
* - REVISIT <AP> 20090617
|
||||
* This should be subject to different algorithms, such
|
||||
* as apply to oldest charges first, newest first, to fees
|
||||
* before rent, etc. Until we get there, I'll hardcode
|
||||
* whatever algorithm is simplest.
|
||||
*/
|
||||
|
||||
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
|
||||
$A = new Account();
|
||||
return $A->reconcileNewLedgerEntry
|
||||
($this->accountId($id), $fundamental_type, $amount, array('LedgerEntry.lease_id' => $id));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -153,7 +96,7 @@ class Lease extends AppModel {
|
||||
('all',
|
||||
array('link' =>
|
||||
array(// Models
|
||||
'LedgerEntry' => array
|
||||
'DoubleEntry' => array
|
||||
('Ledger' => array
|
||||
('fields' => array(),
|
||||
'Account' => array
|
||||
@@ -161,12 +104,12 @@ class Lease extends AppModel {
|
||||
'Ledger' => array
|
||||
('alias' => 'Lx',
|
||||
'fields' => array(),
|
||||
'LedgerEntry' => array
|
||||
'DoubleEntry' => array
|
||||
('alias' => 'LEx',
|
||||
'fields' => array(),
|
||||
'conditions' => array
|
||||
('LEx.effective_date = DATE_ADD(LedgerEntry.through_date, INTERVAL 1 day)',
|
||||
'LEx.lease_id = LedgerEntry.lease_id',
|
||||
('LEx.effective_date = DATE_ADD(DoubleEntry.through_date, INTERVAL 1 day)',
|
||||
'LEx.lease_id = DoubleEntry.lease_id',
|
||||
)
|
||||
),
|
||||
),
|
||||
@@ -219,7 +162,7 @@ class Lease extends AppModel {
|
||||
return false;
|
||||
if (count($entries) != 1)
|
||||
return null;
|
||||
return $entries[0]['LedgerEntry']['through_date'];
|
||||
return $entries[0]['DoubleEntry']['through_date'];
|
||||
}
|
||||
|
||||
|
||||
@@ -234,8 +177,8 @@ class Lease extends AppModel {
|
||||
|
||||
// Income / Receipt / Money
|
||||
// debit: A/R credit: Income <-- this entry
|
||||
// debit: Receipt credit: A/R <-- ReceiptLedgerEntry, below
|
||||
// debit: Money credit: Receipt <-- MoneyLedgerEntry, below
|
||||
// debit: Receipt credit: A/R <-- ReceiptDoubleEntry, below
|
||||
// debit: Money credit: Receipt <-- MoneyDoubleEntry, below
|
||||
|
||||
$query = array
|
||||
('link' => array
|
||||
@@ -250,43 +193,43 @@ class Lease extends AppModel {
|
||||
// We're searching for the Receipt<->A/R entries,
|
||||
// which are debits on the A/R account. Find the
|
||||
// reconciling entries to that A/R debit.
|
||||
'DebitReconciliationLedgerEntry' =>
|
||||
array('alias' => 'ReceiptLedgerEntry',
|
||||
'DebitReconciliationDoubleEntry' =>
|
||||
array('alias' => 'ReceiptDoubleEntry',
|
||||
'fields' => array(),
|
||||
|
||||
// Finally, the Money (Cash/Check/etc) Entry is the one
|
||||
// which reconciles our ReceiptLedgerEntry debit
|
||||
'DebitReconciliationLedgerEntry' =>
|
||||
array('alias' => 'MoneyLedgerEntry',
|
||||
'linkalias' => 'MoneyLedgerEntryR',
|
||||
'fields' => array('SUM(COALESCE(MoneyLedgerEntryR.amount,0)) AS paid'),
|
||||
// which reconciles our ReceiptDoubleEntry debit
|
||||
'DebitReconciliationDoubleEntry' =>
|
||||
array('alias' => 'MoneyDoubleEntry',
|
||||
'linkalias' => 'MoneyDoubleEntryR',
|
||||
'fields' => array('SUM(COALESCE(MoneyDoubleEntryR.amount,0)) AS paid'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'fields' => array('LedgerEntry.amount',
|
||||
'DATE_SUB(LedgerEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
|
||||
'fields' => array('DoubleEntry.amount',
|
||||
'DATE_SUB(DoubleEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
|
||||
),
|
||||
|
||||
'group' => 'LedgerEntry.id HAVING paid <> LedgerEntry.amount',
|
||||
'group' => 'DoubleEntry.id HAVING paid <> DoubleEntry.amount',
|
||||
|
||||
'conditions' => array(array('LedgerEntry.lease_id' => $id),
|
||||
array('Account.id' => $this->LedgerEntry->Ledger->Account->rentAccountID()),
|
||||
'conditions' => array(array('DoubleEntry.lease_id' => $id),
|
||||
array('Account.id' => $this->DoubleEntry->Ledger->Account->rentAccountID()),
|
||||
),
|
||||
'order' => array('LedgerEntry.effective_date',
|
||||
'order' => array('DoubleEntry.effective_date',
|
||||
),
|
||||
);
|
||||
|
||||
$rent = $this->LedgerEntry->find('first', $query);
|
||||
$rent = $this->DoubleEntry->find('first', $query);
|
||||
if ($rent)
|
||||
return $rent[0]['paid_through'];
|
||||
|
||||
$query['fields'] = 'LedgerEntry.through_date';
|
||||
$query['order'] = 'LedgerEntry.through_date DESC';
|
||||
$query['group'] = 'LedgerEntry.id';
|
||||
$rent = $this->LedgerEntry->find('first', $query);
|
||||
$query['fields'] = 'DoubleEntry.through_date';
|
||||
$query['order'] = 'DoubleEntry.through_date DESC';
|
||||
$query['group'] = 'DoubleEntry.id';
|
||||
$rent = $this->DoubleEntry->find('first', $query);
|
||||
if ($rent)
|
||||
return $rent['LedgerEntry']['through_date'];
|
||||
return $rent['DoubleEntry']['through_date'];
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -495,7 +438,7 @@ class Lease extends AppModel {
|
||||
|
||||
$A = new Account();
|
||||
$stats = $A->stats($A->accountReceivableAccountID(), true,
|
||||
array('LedgerEntry.lease_id' => $id));
|
||||
array('DoubleEntry.lease_id' => $id));
|
||||
|
||||
// Pull to the top level and return
|
||||
$stats = $stats['Ledger'];
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<?php
|
||||
class Ledger extends AppModel {
|
||||
|
||||
var $name = 'Ledger';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'name' => array('notempty'),
|
||||
);
|
||||
|
||||
var $belongsTo = array(
|
||||
'Account',
|
||||
'PriorLedger' => array('className' => 'Ledger'),
|
||||
@@ -14,32 +8,33 @@ class Ledger extends AppModel {
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry' => array(
|
||||
'DoubleEntry' => array(
|
||||
'foreignKey' => false,
|
||||
|
||||
// conditions will be used when JOINing tables
|
||||
// (such as find with LinkableBehavior)
|
||||
'conditions' => array('OR' =>
|
||||
array('LedgerEntry.debit_ledger_id = %{MODEL_ALIAS}.id',
|
||||
'LedgerEntry.credit_ledger_id = %{MODEL_ALIAS}.id')),
|
||||
array('DoubleEntry.debit_ledger_id = %{MODEL_ALIAS}.id',
|
||||
'DoubleEntry.credit_ledger_id = %{MODEL_ALIAS}.id')),
|
||||
|
||||
// finderQuery will be used when tables are put
|
||||
// together across several querys, not with JOIN.
|
||||
// (such as find with ContainableBehavior)
|
||||
'finderQuery' => 'SELECT `LedgerEntry`.*
|
||||
FROM pmgr_ledger_entries AS `LedgerEntry`
|
||||
WHERE LedgerEntry.debit_ledger_id = ({$__cakeID__$})
|
||||
OR LedgerEntry.credit_ledger_id = ({$__cakeID__$})',
|
||||
'finderQuery' => 'SELECT `DoubleEntry`.*
|
||||
FROM pmgr_double_entries AS `DoubleEntry`
|
||||
WHERE DoubleEntry.debit_ledger_id = ({$__cakeID__$})
|
||||
OR DoubleEntry.credit_ledger_id = ({$__cakeID__$})',
|
||||
|
||||
'counterQuery' => ''
|
||||
),
|
||||
|
||||
'DebitLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'className' => 'DoubleEntry',
|
||||
'foreignKey' => 'debit_ledger_id',
|
||||
'dependent' => false,
|
||||
),
|
||||
'CreditLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'className' => 'DoubleEntry',
|
||||
'foreignKey' => 'credit_ledger_id',
|
||||
'dependent' => false,
|
||||
),
|
||||
@@ -124,7 +119,7 @@ class Ledger extends AppModel {
|
||||
'comment' => "Ledger Balance Forward",
|
||||
);
|
||||
|
||||
$carry_entry = new LedgerEntry();
|
||||
$carry_entry = new DoubleEntry();
|
||||
$carry_entry->create();
|
||||
if (!$carry_entry->save($carry_entry_data, false)) {
|
||||
return null;
|
||||
@@ -137,11 +132,11 @@ class Ledger extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findLedgerEntries
|
||||
* function: ledgerEntries
|
||||
* - Returns an array of ledger entries that belong to a given
|
||||
* ledger. There is extra work done... see the LedgerEntry model.
|
||||
* ledger. There is extra work done... see the DoubleEntry model.
|
||||
*/
|
||||
function findLedgerEntries($id, $account_type = null, $cond = null, $link = null) {
|
||||
function ledgerEntries($id, $account_type = null, $cond = null, $link = null) {
|
||||
/* pr(array('function' => 'Ledger::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
|
||||
/* )); */
|
||||
@@ -177,7 +172,7 @@ class Ledger extends AppModel {
|
||||
'credit' => $stats['credits'],
|
||||
'balance' => $stats['balance']),
|
||||
|
||||
'LedgerEntry' => array('id' => null,
|
||||
'DoubleEntry' => array('id' => null,
|
||||
//'comment' => "Balance Forward from $date"),
|
||||
'comment' => "-- SUMMARY OF EXCLUDED ENTRIES --"),
|
||||
|
||||
@@ -188,7 +183,7 @@ class Ledger extends AppModel {
|
||||
));
|
||||
}
|
||||
|
||||
$entries = $this->LedgerEntry->findInLedgerContext($id, $account_type, $cond, $link);
|
||||
$entries = $this->DoubleEntry->findInLedgerContext($id, $account_type, $cond, $link);
|
||||
/* pr(array('function' => 'Ledger::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
|
||||
/* 'vars' => compact('ledger'), */
|
||||
@@ -214,23 +209,23 @@ class Ledger extends AppModel {
|
||||
('link' =>
|
||||
array(// Models
|
||||
'Account' => array('fields' => array()),
|
||||
//'LedgerEntry' => array('fields' => array()),
|
||||
'LedgerEntry' =>
|
||||
//'DoubleEntry' => array('fields' => array()),
|
||||
'DoubleEntry' =>
|
||||
array('fields' => array(),
|
||||
'Transaction' => array('fields' => array('stamp')),
|
||||
),
|
||||
),
|
||||
'fields' =>
|
||||
array("SUM(IF(LedgerEntry.debit_ledger_id = Ledger.id,
|
||||
LedgerEntry.amount, NULL)) AS debits",
|
||||
"SUM(IF(LedgerEntry.credit_ledger_id = Ledger.id,
|
||||
LedgerEntry.amount, NULL)) AS credits",
|
||||
array("SUM(IF(DoubleEntry.debit_ledger_id = Ledger.id,
|
||||
DoubleEntry.amount, NULL)) AS debits",
|
||||
"SUM(IF(DoubleEntry.credit_ledger_id = Ledger.id,
|
||||
DoubleEntry.amount, NULL)) AS credits",
|
||||
"SUM(IF(Account.type IN ('ASSET', 'EXPENSE'),
|
||||
IF(LedgerEntry.debit_ledger_id = Ledger.id, 1, -1),
|
||||
IF(LedgerEntry.credit_ledger_id = Ledger.id, 1, -1)
|
||||
) * IF(LedgerEntry.amount, LedgerEntry.amount, 0)
|
||||
IF(DoubleEntry.debit_ledger_id = Ledger.id, 1, -1),
|
||||
IF(DoubleEntry.credit_ledger_id = Ledger.id, 1, -1)
|
||||
) * IF(DoubleEntry.amount, DoubleEntry.amount, 0)
|
||||
) AS balance",
|
||||
"COUNT(LedgerEntry.id) AS entries"),
|
||||
"COUNT(DoubleEntry.id) AS entries"),
|
||||
'conditions' => $cond,
|
||||
'group' => 'Ledger.id',
|
||||
));
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,59 @@
|
||||
<?php
|
||||
class MonetarySource extends AppModel {
|
||||
|
||||
var $name = 'MonetarySource';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'name' => array('notempty'),
|
||||
'tillable' => array('boolean')
|
||||
);
|
||||
class Payment extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'Customer',
|
||||
'Lease',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
var $hasAndBelongsToMany = array(
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addPayment
|
||||
* - Adds a new payment
|
||||
*/
|
||||
|
||||
function addPayment($data, $customer_id, $lease_id = null, $reconcile = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key payment parameters
|
||||
$payment = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1,
|
||||
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1));
|
||||
$payment['customer_id'] = $customer_id;
|
||||
|
||||
// Create the payment entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a payment.
|
||||
$ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
|
||||
($payment,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->paymentAccountID())
|
||||
) + $entry,
|
||||
array('debit' => 'payment',
|
||||
'credit' => $reconcile)
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
$payment = array_intersect_key($ids,
|
||||
array('payment_id'=>1,
|
||||
'split_payment_id'=>1));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -48,7 +87,7 @@ class MonetarySource extends AppModel {
|
||||
*/
|
||||
|
||||
function nsf($id, $stamp = null) {
|
||||
pr(array('MonetarySource::nsf',
|
||||
pr(array('Payment::nsf',
|
||||
compact('id')));
|
||||
|
||||
$A = new Account();
|
||||
@@ -58,9 +97,9 @@ class MonetarySource extends AppModel {
|
||||
('first',
|
||||
array('contain' =>
|
||||
array(/* e3 */
|
||||
'LedgerEntry' =>
|
||||
'DoubleEntry' =>
|
||||
array('Transaction.id',
|
||||
'MonetarySource.id',
|
||||
'Payment.id',
|
||||
'Customer.id',
|
||||
'Lease.id',
|
||||
|
||||
@@ -87,7 +126,7 @@ class MonetarySource extends AppModel {
|
||||
),
|
||||
|
||||
/* e2 */
|
||||
'DebitReconciliationLedgerEntry' =>
|
||||
'DebitReconciliationDoubleEntry' =>
|
||||
array(/* e2 credit */
|
||||
'CreditLedger' => /* i.e. A/R Ledger */
|
||||
array('fields' => array(),
|
||||
@@ -102,11 +141,11 @@ class MonetarySource extends AppModel {
|
||||
/* e1 */
|
||||
// STUPID CakePHP bug screws up CLASS contains CLASS.
|
||||
// Use the same class, but with different name.
|
||||
'DebitReconciliationLedgerEntry2',
|
||||
'DebitReconciliationDoubleEntry2',
|
||||
),
|
||||
|
||||
/* e4 */
|
||||
'CreditReconciliationLedgerEntry' =>
|
||||
'CreditReconciliationDoubleEntry' =>
|
||||
array(/* e4 debit */
|
||||
'DebitLedger' => /* e.g. BANK Ledger */
|
||||
array('fields' => array('id'),
|
||||
@@ -120,7 +159,7 @@ class MonetarySource extends AppModel {
|
||||
),
|
||||
),
|
||||
|
||||
'conditions' => array(array('MonetarySource.id' => $id)),
|
||||
'conditions' => array(array('Payment.id' => $id)),
|
||||
));
|
||||
pr($source);
|
||||
|
||||
@@ -131,9 +170,9 @@ class MonetarySource extends AppModel {
|
||||
|
||||
$t4_id = null;
|
||||
$t5_id = null;
|
||||
foreach ($source['LedgerEntry'] AS $e3) {
|
||||
foreach ($source['DoubleEntry'] AS $e3) {
|
||||
// We expect only a single e4 entry
|
||||
$e4 = $e3['CreditReconciliationLedgerEntry'];
|
||||
$e4 = $e3['CreditReconciliationDoubleEntry'];
|
||||
if (count($e4) < 1)
|
||||
continue;
|
||||
if (count($e4) > 1)
|
||||
@@ -149,7 +188,7 @@ class MonetarySource extends AppModel {
|
||||
$bank_account_id = $e4['DebitLedger']['account_id'];
|
||||
|
||||
// post new e5
|
||||
$e5_ids = $A->postLedgerEntry
|
||||
$e5_ids = $A->postDoubleEntry
|
||||
(array('transaction_id' => $t4_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($bank_account_id),
|
||||
@@ -172,7 +211,7 @@ class MonetarySource extends AppModel {
|
||||
// post new e6... this will be our crossover point
|
||||
// from typical positive entries to negative entries.
|
||||
// Therefore, no reconciliation on this account.
|
||||
$e6_ids = $A->postLedgerEntry
|
||||
$e6_ids = $A->postDoubleEntry
|
||||
(array('transaction_id' => $t5_id),
|
||||
array('monetary_source_id' => $e3['monetary_source_id']),
|
||||
array('debit_ledger_id' => $A->currentLedgerID($nsf_account_id),
|
||||
@@ -184,7 +223,7 @@ class MonetarySource extends AppModel {
|
||||
'comment' => "NSF tracker; Monetary Source #{$id}",
|
||||
),
|
||||
array('debit' => array
|
||||
(array('LedgerEntry' =>
|
||||
(array('DoubleEntry' =>
|
||||
array('id' => $e5_ids['id'],
|
||||
'amount' => $amount))),
|
||||
)
|
||||
@@ -198,12 +237,12 @@ class MonetarySource extends AppModel {
|
||||
compact('e6_ids', 'amount')));
|
||||
|
||||
$t6_id = null;
|
||||
foreach ($e3['DebitReconciliationLedgerEntry'] AS $e2) {
|
||||
foreach ($e2['DebitReconciliationLedgerEntry2'] AS $e1) {
|
||||
foreach ($e3['DebitReconciliationDoubleEntry'] AS $e2) {
|
||||
foreach ($e2['DebitReconciliationDoubleEntry2'] AS $e1) {
|
||||
$amount = -1*$e1['Reconciliation']['amount'];
|
||||
|
||||
// post new e7
|
||||
$e7_ids = $A->postLedgerEntry
|
||||
$e7_ids = $A->postDoubleEntry
|
||||
(array('transaction_id' => $t6_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($receipt_account_id),
|
||||
@@ -215,12 +254,12 @@ class MonetarySource extends AppModel {
|
||||
'comment' => "NSF Receipt; Monetary Source #{$id}",
|
||||
),
|
||||
array('debit' => array
|
||||
(array('LedgerEntry' =>
|
||||
(array('DoubleEntry' =>
|
||||
array('id' => $e6_ids['id'],
|
||||
'amount' => $amount))),
|
||||
|
||||
'credit' => array
|
||||
(array('LedgerEntry' =>
|
||||
(array('DoubleEntry' =>
|
||||
array('id' => $e1['id'],
|
||||
'amount' => $amount))),
|
||||
)
|
||||
@@ -237,11 +276,11 @@ class MonetarySource extends AppModel {
|
||||
}
|
||||
|
||||
// Cheat for now
|
||||
$customer_id = $source['LedgerEntry'][0]['customer_id'];
|
||||
$customer_id = $source['DoubleEntry'][0]['customer_id'];
|
||||
$lease_id = null;
|
||||
|
||||
// post new e8
|
||||
$e8_ids = $A->postLedgerEntry
|
||||
$e8_ids = $A->postDoubleEntry
|
||||
(array('transaction_id' => $t6_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),
|
||||
@@ -2,13 +2,11 @@
|
||||
class Reconciliation extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'DebitLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
//'foreignKey' => 'credit_ledger_entry_id',
|
||||
'DebitEntry' => array(
|
||||
'className' => 'Entry',
|
||||
),
|
||||
'CreditLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
//'foreignKey' => 'credit_ledger_entry_id',
|
||||
'CreditEntry' => array(
|
||||
'className' => 'Entry',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?php
|
||||
class Transaction extends AppModel {
|
||||
|
||||
var $name = 'Transaction';
|
||||
|
||||
var $validate = array(
|
||||
'stamp' => array('date')
|
||||
);
|
||||
|
||||
var $belongsTo = array(
|
||||
'Customer',
|
||||
'Lease',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class Transaction extends AppModel {
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addInvoice
|
||||
* - Adds a new invoice transaction
|
||||
* - Adds a new invoice invoice
|
||||
*/
|
||||
|
||||
function addInvoice($data, $customer_id, $lease_id = null) {
|
||||
@@ -31,32 +31,31 @@ class Transaction extends AppModel {
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key invoice parameters
|
||||
$invoice = array_intersect_key($data, array('Invoice'=>1));
|
||||
$invoice['type'] = 'INVOICE';
|
||||
|
||||
// Determine the total charges on the invoice
|
||||
$grand_total = 0;
|
||||
foreach ($data['LedgerEntry'] AS $entry)
|
||||
$grand_total += $entry['amount'];
|
||||
$invoice['amount'] = 0;
|
||||
foreach ($data['DoubleEntry'] AS $entry)
|
||||
$invoice['amount'] += $entry['amount'];
|
||||
|
||||
// Go through the entered charges
|
||||
$invoice_transaction = array_intersect_key($data, array('Transaction'=>1, 'transaction_id'=>1));
|
||||
foreach ($data['LedgerEntry'] AS $entry) {
|
||||
foreach ($data['DoubleEntry'] AS $entry) {
|
||||
//pr(compact('entry'));
|
||||
// Create the receipt entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a payment.
|
||||
$ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry
|
||||
($invoice_transaction,
|
||||
array_intersect_key($entry, array('MonetarySource'=>1))
|
||||
+ array_intersect_key($entry, array('account_id'=>1)),
|
||||
$ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
|
||||
($invoice,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()),
|
||||
'credit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'customer_id' => $customer_id,
|
||||
'lease_id' => $lease_id)
|
||||
+ $entry
|
||||
'credit_ledger_id' => $A->currentLedgerID($entry['account_id'])
|
||||
) + $entry
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
$invoice_transaction = array_intersect_key($ids, array('transaction_id'=>1));
|
||||
$invoice = array_intersect_key($ids, array('invoice_id'=>1));
|
||||
}
|
||||
|
||||
return $ret;
|
||||
@@ -67,7 +66,7 @@ class Transaction extends AppModel {
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addReceipt
|
||||
* - Adds a new receipt transaction
|
||||
* - Adds a new receipt
|
||||
*/
|
||||
|
||||
function addReceipt($data, $customer_id, $lease_id = null) {
|
||||
@@ -77,32 +76,40 @@ class Transaction extends AppModel {
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Go through the entered payments
|
||||
$receipt_transaction = array_intersect_key($data, array('Transaction'=>1, 'transaction_id'=>1));
|
||||
foreach ($data['LedgerEntry'] AS $entry) {
|
||||
// Create the receipt entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a payment.
|
||||
$ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry
|
||||
($receipt_transaction,
|
||||
array_intersect_key($entry, array('MonetarySource'=>1))
|
||||
+ array_intersect_key($entry, array('account_id'=>1)),
|
||||
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()),
|
||||
'customer_id' => $customer_id,
|
||||
'lease_id' => $lease_id)
|
||||
+ $entry,
|
||||
'receipt');
|
||||
// Establish the key receipt parameters
|
||||
$receipt = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1,
|
||||
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1));
|
||||
$receipt['type'] = 'RECEIPT';
|
||||
|
||||
// Determine the total charges on the receipt
|
||||
$receipt['amount'] = 0;
|
||||
foreach ($data['DoubleEntry'] AS $entry)
|
||||
$receipt['amount'] += $entry['amount'];
|
||||
|
||||
// Go through the entered charges
|
||||
foreach ($data['DoubleEntry'] AS $entry) {
|
||||
// Create the receipt entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a receipt.
|
||||
$ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
|
||||
($receipt,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID())
|
||||
) + $entry,
|
||||
array('debit' => 'receipt',
|
||||
'credit' => $reconcile)
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
$receipt_transaction = array_intersect_key($ids,
|
||||
array('transaction_id'=>1,
|
||||
'split_transaction_id'=>1));
|
||||
$receipt = array_intersect_key($ids,
|
||||
array('receipt_id'=>1,
|
||||
'split_receipt_id'=>1));
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user