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/site@352 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
abijah
2009-07-19 23:35:25 +00:00
parent af0c10f6a6
commit 3d262fd4db
24 changed files with 2401 additions and 1373 deletions

View File

@@ -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));

View File

@@ -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,

View File

@@ -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
models/double_entry.php Normal file
View 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
models/entry.php Normal file
View 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;
}
}

View File

@@ -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'];

View File

@@ -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',
));

View File

@@ -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;
}
}

View File

@@ -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),

View File

@@ -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',
),
);

View File

@@ -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;
}
}
?>