array('numeric'), 'name' => array('notempty'), 'external_name' => array('notempty') ); var $hasOne = array( 'CurrentLedger' => array( 'className' => 'Ledger', 'conditions' => array('NOT' => array('CurrentLedger.closed')) ), ); var $hasMany = array( 'Ledger', ); /************************************************************************** ************************************************************************** ************************************************************************** * function: type * - Returns the type of this account */ function type($id) { $this->cacheQueries = true; $account = $this->find('first', array ('recursive' => -1, 'fields' => array('type'), 'conditions' => array(array('Account.id' => $id)), )); $this->cacheQueries = false; return $account['Account']['type']; } /************************************************************************** ************************************************************************** ************************************************************************** * function: fundamentalType * - Returns the fundmental type of the account, credit or debit */ function fundamentalType($id_or_type) { if (is_numeric($id_or_type)) $type = $this->type($id_or_type); else $type = $id_or_type; // Asset and Expense accounts are debit accounts if (in_array($type, array('ASSET', 'EXPENSE'))) return 'debit'; // Otherwise, it's a credit account return 'credit'; } /************************************************************************** ************************************************************************** ************************************************************************** * function: fundamentalOpposite * - Returns the opposite fundmental type of the account, credit or debit */ function fundamentalOpposite($id_or_type) { $fund = $this->fundamentalType($id_or_type); if ($fund == 'debit') return 'credit'; return 'debit'; } /************************************************************************** ************************************************************************** ************************************************************************** * function: securityDepositAccountID * - Returns the ID of the Security Deposit Account */ function securityDepositAccountID() { $this->cacheQueries = true; $account = $this->find('first', array ('recursive' => -1, 'conditions' => array (array('name' => 'Security Deposit')), )); $this->cacheQueries = false; return $account['Account']['id']; } /************************************************************************** ************************************************************************** ************************************************************************** * function:rentAccountID * - Returns the ID of the Rent Account */ function rentAccountID() { $this->cacheQueries = true; $account = $this->find('first', array ('recursive' => -1, 'conditions' => array (array('name' => 'Rent')), )); $this->cacheQueries = false; return $account['Account']['id']; } /************************************************************************** ************************************************************************** ************************************************************************** * function: ledgers * - Returns an array of ledger ids from the given account */ function ledgers($id, $all = false) { $cachekey = $all ? 'all' : 'current'; if (isset($this->cache[$id]['ledgers'][$cachekey])) { return $this->cache[$id]['ledgers'][$cachekey]; } if ($all) { $contain = array('Ledger' => array('fields' => array('Ledger.id'))); } else { $contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id'))); } $account = $this->find('first', array ('contain' => $contain, 'fields' => array(), 'conditions' => array(array('Account.id' => $id)), )); if ($all) { $ledger_ids = array(); foreach ($account['Ledger'] AS $ledger) array_push($ledger_ids, $ledger['id']); } else { $ledger_ids = array($account['CurrentLedger']['id']); } // Save the ledgers in our cache for future reference $this->cache[$id]['ledgers'][$cachekey] = $ledger_ids; /* pr(array('function' => 'Account::ledgers', */ /* 'args' => compact('id', 'all'), */ /* 'return' => $this->cache[$id]['ledgers'][$cachekey])); */ return $this->cache[$id]['ledgers'][$cachekey]; } /************************************************************************** ************************************************************************** ************************************************************************** * function: findLedgerEntries * - 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) { /* 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); } $stats = $this->stats($id, $all, $cond); $entries = array('Entries' => $entries, 'summary' => $stats['Ledger']); /* pr(array('function' => 'Account::findLedgerEntries', */ /* 'args' => compact('id', 'all', 'cond', 'link'), */ /* 'vars' => compact('stats'), */ /* 'return' => compact('entries'), */ /* )); */ return $entries; } /************************************************************************** ************************************************************************** ************************************************************************** * function: findLedgerEntriesRelatedToAccount * - Returns an array of ledger entries that belong to the given * account, and are related to a specific account, either just from * the current ledger, or from all ledgers. */ function findLedgerEntriesRelatedToAccount($id, $rel_ids, $all = false, $cond = null, $link = null) { /* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */ /* 'args' => compact('id', 'rel_ids', 'all', 'cond', 'link'), */ /* )); */ if (!isset($cond)) $cond = array(); if (!is_array($rel_ids)) $rel_ids = array($rel_ids); $ledger_ids = array(); 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)); $entries = $this->findLedgerEntries($id, $all, $cond, $link); /* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */ /* 'args' => compact('id', 'relid', 'all', 'cond', 'link'), */ /* 'vars' => compact('ledger_ids'), */ /* 'return' => compact('entries'), */ /* )); */ return $entries; } /************************************************************************** ************************************************************************** ************************************************************************** * function: findUnreconciledLedgerEntries * - Returns ledger entries that are not yet reconciled * (such as charges not paid). */ function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) { foreach (($fundamental_type ? array($fundamental_type) : array('debit', 'credit')) AS $fund) { $ucfund = ucfirst($fund); $unreconciled[$fund]['entry'] = $this->find ('all', array ('link' => array ('Ledger' => array ('fields' => array(), "LedgerEntry" => array ('class' => "{$ucfund}LedgerEntry", 'fields' => array('id', 'amount'), "ReconciliationLedgerEntry" => array ('class' => "{$ucfund}ReconciliationLedgerEntry", 'fields' => array ("COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'", "LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'", ), ), ), ), ), 'group' => ("LedgerEntry.id" . " HAVING LedgerEntry.amount" . " <> COALESCE(SUM(Reconciliation.amount),0)"), 'conditions' => array('Account.id' => $id), 'fields' => array(), )); $balance = 0; foreach ($unreconciled[$fund]['entry'] AS &$entry) { $entry = array_merge(array_diff_key($entry["LedgerEntry"], array(0=>true)), $entry[0]); $balance += $entry['balance']; } $unreconciled[$fund]['balance'] = $balance; } return $unreconciled; } /************************************************************************** ************************************************************************** ************************************************************************** * function: reconcileNewLedgerEntry * - Returns which ledger entries a new credit/debit would * reconcile, and how much. * * - REVISIT 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) { $ofund = $this->fundamentalOpposite($fundamental_type); $unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0)); $applied = 0; // if there is no money in the entry, it can reconcile nothing // don't bother wasting time sifting ledger entries. if ($amount > 0) { $unreconciled = $this->findUnreconciledLedgerEntries($id, $ofund); foreach ($unreconciled[$ofund]['entry'] AS $i => &$entry) { // Determine if amount is sufficient to cover the entry if ($amount > $entry['balance']) $apply = $entry['balance']; elseif ($amount > 0) $apply = $amount; else { unset($unreconciled[$i]); continue; } $entry['applied'] = $apply; $entry['reconciled'] += $apply; $entry['balance'] -= $apply; $applied += $apply; $amount -= $apply; } } $unreconciled[$ofund]['unapplied'] = $amount; $unreconciled[$ofund]['applied'] = $applied; $unreconciled[$ofund]['balance'] -= $applied; return $unreconciled; } /************************************************************************** ************************************************************************** ************************************************************************** * function: stats * - Returns summary data from the requested account. */ function stats($id = null, $all = false, $cond = null) { if (!$id) return null; // All old, closed ledgers MUST balance to 0. // However, the user may want the ENTIRE running totals, // (not just the balance), so we may have to query all // ledgers, as dictated by the $all parameter. $account = $this->find('first', array('contain' => ($all ? array('Ledger' => array ('fields' => array('id'))) : array('CurrentLedger' => array ('fields' => array('id'))) ), 'conditions' => array (array('Account.id' => $id)) )); $stats = array(); if ($all) { foreach ($account['Ledger'] AS $ledger) $this->statsMerge($stats['Ledger'], $this->Ledger->stats($ledger['id'], $cond)); } else { $stats['Ledger'] = $this->Ledger->stats($account['CurrentLedger']['id'], $cond); } return $stats; } } ?>