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: reconciledSet * - Returns the set of entries satisfying the given conditions, * along with any entries that they reconcile */ function reconciledSet($set, $cond = null, $link = null, $unrec = false) { if (!isset($cond)) $cond = array(); if (!isset($link)) $link = array(); if ($set == 'CHARGE' || $set == 'PAYMENT') $cond[] = array('Entry.type' => $set); elseif ($set == 'DEBIT' || $set == 'CREDIT') $cond[] = array('Entry.crdr' => $set); else die("INVALID RECONCILE SET"); $link['DoubleEntry'] += array(); /* if ($set == 'CHARGE') */ /* $link['Payment'] = array('fields' => array("SUM(ChargesPayment.amount) AS applied")); */ /* if ($set == 'PAYMENT') */ /* $link['Charge'] = array('fields' => array("SUM(ChargesPayment.amount) AS applied")); */ /* if ($set == 'DEBIT') */ /* $link['Credit'] = array('fields' => array("SUM(Reconciliation.amount) AS applied")); */ /* if ($set == 'CREDIT') */ /* $link['Debit'] = array('fields' => array("SUM(Reconciliation.amount) AS applied")); */ if ($set == 'CHARGE') $link['Payment'] = array('fields' => array("SUM(AppliedPayment.amount) AS applied")); if ($set == 'PAYMENT') $link['Charge'] = array('fields' => array("SUM(AppliedCharge.amount) AS applied")); if ($set == 'DEBIT') $link['Credit'] = array('fields' => array("SUM(AppliedCredit.amount) AS applied")); if ($set == 'CREDIT') $link['Debit'] = array('fields' => array("SUM(AppliedDebit.amount) AS applied")); $result = $this->find ('all', array ( 'link' => $link, 'conditions' => $cond, 'group' => 'Entry.id', )); pr(array('reconciledSet', compact('set', 'cond', 'link', 'result'))); $resultset = array(); foreach ($result AS $i => $entry) { $entry['DoubleEntry'] += $entry[0]; unset($entry[0]); $entry['DoubleEntry']['balance'] = $entry['DoubleEntry']['amount'] - $entry['DoubleEntry']['applied']; // Since HAVING isn't a builtin feature of CakePHP, // we'll have to post-process to get the desired entries if ($entry['DoubleEntry']['balance'] == 0) { if (!$unrec) $resultset[] = $entry; } else { if ($unrec) $resultset[] = $entry; } } //$result['stats'] = $this->stats(null, $cond); pr($this->stats(null, $cond, $link)); return $resultset; } /************************************************************************** ************************************************************************** ************************************************************************** * function: reconciledEntries * - Returns a list of entries that reconcile against the given entry. * (such as payments towards a charge). */ function reconciledEntries($id, $cond = null, $link = null) { if (!isset($cond)) $cond = array(); if (!isset($link)) $link = array(); $cond[] = array('Entry.id' => $id); $entry = $this->find('first', array('conditions' => array('Entry.id' => $id), '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 = null, $cond = null, $link = null) { if (!isset($cond)) $cond = array(); if (!isset($link)) $link = array(); if (isset($id)) $cond[] = array('Entry.id' => $id); /* $entry = $this->find('first', array('contain' => array('DoubleEntry.amount'), */ /* 'fields' => array('Entry.type', 'Entry.crdr'), */ /* 'conditions' => array('Entry.id' => $id))); */ /* pr(compact('entry')); */ /* $entry['Entry'] += $entry['DoubleEntry']; */ /* $entry = $entry['Entry']; */ $stats = array(); foreach(array('charge', 'payment', 'debit', 'credit') AS $rtype) { $Rtype = ucfirst($rtype); $rlink = $link; $rlink[$Rtype] = array('fields' => array("SUM(Applied{$Rtype}.amount) AS applied")); pr(array('stats()', compact('id', 'cond', 'link', 'rlink'))); if (1 || ($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' => $rlink, 'fields' => array("SUM(DoubleEntry.amount) AS total", "SUM(DoubleEntry.amount) - SUM(Applied{$Rtype}.amount) AS unapplied"), 'conditions' => $cond, //'group' => 'Entry.id', )); pr(compact('Rtype', 'result')); $sumfld = $Rtype; $stats[$sumfld] = $result[0]; /* if (!isset($stats[$sumfld]['applied'])) */ /* $stats[$sumfld]['applied'] = 0; */ } } 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; } }