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