diff --git a/models/customer.php b/models/customer.php index 57f0036..123ee80 100644 --- a/models/customer.php +++ b/models/customer.php @@ -372,6 +372,7 @@ class Customer extends AppModel { 'fields' => $this->StatementEntry->chargePaymentFields(true), 'conditions' => array('StatementEntry.customer_id' => $id), )); + $find_stats = $find_stats[0]; pr(compact('find_stats')); $tquery = $query; @@ -395,7 +396,8 @@ class Customer extends AppModel { $ar_transaction_stats += $ar_transaction_stats['LedgerEntry']; pr(compact('ar_transaction_stats')); - $stats = $ar_transaction_stats; + //$stats = $ar_transaction_stats; + $stats = $find_stats; return $stats; } diff --git a/models/double_entry.php b/models/double_entry.php index 4c6d84b..31fd883 100644 --- a/models/double_entry.php +++ b/models/double_entry.php @@ -10,4 +10,86 @@ class DoubleEntry extends AppModel { ), ); + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyDoubleEntry + * - Verifies consistenty of new double entry data + * (not in a pre-existing double entry) + */ + function verifyDoubleEntry($entry1, $entry2, $entry1_tender = null) { + pr(array("DoubleEntry::verifyDoubleEntry()" + => compact('entry1', 'entry2', 'entry1_tender'))); + + $LE = new LedgerEntry(); + if (!$LE->verifyLedgerEntry($entry1, $entry1_tender)) { + pr(array("DoubleEntry::verifyDoubleEntry()" + => "Entry1 verification failed")); + return false; + } + if (!$LE->verifyLedgerEntry($entry2)) { + pr(array("DoubleEntry::verifyDoubleEntry()" + => "Entry2 verification failed")); + return false; + } + + if (!(($entry1['crdr'] === 'DEBIT' && $entry2['crdr'] === 'CREDIT') || + ($entry1['crdr'] === 'CREDIT' && $entry2['crdr'] === 'DEBIT'))) { + pr(array("DoubleEntry::verifyDoubleEntry()" + => "Double Entry verification failed")); + return false; + } + + return true; + } + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addDoubleEntry + * - Inserts new Double Entry into the database + */ + + function addDoubleEntry($entry1, $entry2, $entry1_tender = null) { + pr(array('DoubleEntry::addDoubleEntry' => + compact('entry1', 'entry2', 'entry1_tender'))); + + $ret = array(); + if (!$this->verifyDoubleEntry($entry1, $entry2, $entry1_tender)) + return array('error' => true) + $ret; + + // Since this model only relates to DebitEntry and CreditEntry... + $LE = new LedgerEntry(); + + // Add the first ledger entry to the database + $result = $LE->addLedgerEntry($entry1, $entry1_tender); + $ret['Entry1'] = $result; + if ($result['error']) + return array('error' => true) + $ret; + + // Add the second ledger entry to the database + $result = $LE->addLedgerEntry($entry2); + $ret['Entry2'] = $result; + if ($result['error']) + return array('error' => true) + $ret; + + // Now link them as a double entry + $double_entry = array(); + $double_entry['debit_entry_id'] = + ($entry1['crdr'] === 'DEBIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id']; + $double_entry['credit_entry_id'] = + ($entry1['crdr'] === 'CREDIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id']; + + pr(array('DoubleEntry::addDoubleEntry' => + array('checkpoint' => 'Pre-Save') + + compact('double_entry'))); + + $this->create(); + if (!$this->save($double_entry)) + return array('error' => true) + $ret; + + $ret['double_entry_id'] = $this->id; + return $ret + array('error' => false); + } } diff --git a/models/ledger_entry.php b/models/ledger_entry.php index 8c5f905..5f5c038 100644 --- a/models/ledger_entry.php +++ b/models/ledger_entry.php @@ -9,6 +9,7 @@ class LedgerEntry extends AppModel { var $hasOne = array( 'Tender', + 'DoubleEntry', ); var $hasMany = array( @@ -78,189 +79,74 @@ class LedgerEntry extends AppModel { /************************************************************************** ************************************************************************** ************************************************************************** - * function: addCharge - * - Adds a new charge + * function: verifyLedgerEntry + * - Verifies consistenty of new ledger entry data + * (not in a pre-existing ledger entry) */ + function verifyLedgerEntry($entry, $tender = null) { + pr(array("LedgerEntry::verifyLedgerEntry()" + => compact('entry', 'tender'))); - function addCharge($data, $transaction, $customer_id, $lease_id = null) { - // Create some models for convenience - $A = new Account(); + if (empty($entry['account_id']) || + empty($entry['crdr']) || + empty($entry['amount']) + ) { + pr(array("LedgerEntry::verifyLedgerEntry()" + => "Entry verification failed")); + return false; + } - // 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->postLedgerEntry - ($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->postLedgerEntry - ($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: 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'))); + if (isset($tender) && !$this->Tender->verifyTender($tender)) { + pr(array("LedgerEntry::verifyLedgerEntry()" + => "Tender verification failed")); + return false; } return true; } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addLedgerEntry + * - Inserts new Ledger Entry into the database + */ + function addLedgerEntry($entry, $tender = null) { + pr(array('LedgerEntry::addLedgerEntry' => + compact('entry', 'tender'))); + + $ret = array(); + if (!$this->verifyLedgerEntry($entry, $tender)) + return array('error' => true) + $ret; + + if (empty($entry['ledger_id'])) + $entry['ledger_id'] = + $this->Account->currentLedgerID($entry['account_id']); + + pr(array('LedgerEntry::addLedgerEntry' => + array('checkpoint' => 'Pre-Save') + + compact('entry'))); + + $this->create(); + if (!$this->save($entry)) + return array('error' => true) + $ret; + + $ret['ledger_entry_id'] = $this->id; + + if (isset($tender)) { + $tender['account_id'] = $entry['account_id']; + $tender['ledger_entry_id'] = $ret['ledger_entry_id']; + $result = $this->Tender->addTender($tender); + $ret['Tender'] = $result; + if ($result['error']) + return array('error' => true) + $ret; + } + + return $ret + array('error' => false); + } + + /************************************************************************** ************************************************************************** ************************************************************************** diff --git a/models/statement_entry.php b/models/statement_entry.php index d7a97f2..ff3ec25 100644 --- a/models/statement_entry.php +++ b/models/statement_entry.php @@ -56,6 +56,58 @@ class StatementEntry extends AppModel { } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyStatementEntry + * - Verifies consistenty of new statement entry data + * (not in a pre-existing statement entry) + */ + function verifyStatementEntry($entry) { + pr(array("StatementEntry::verifyStatementEntry()" + => compact('entry'))); + + if (empty($entry['type']) || + //empty($entry['effective_date']) || + empty($entry['account_id']) || + empty($entry['amount']) + ) { + pr(array("StatementEntry::verifyStatementEntry()" + => "Entry verification failed")); + return false; + } + + return true; + } + + + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: addStatementEntry + * - Inserts new Statement Entry into the database + */ + function addStatementEntry($entry) { + pr(array('StatementEntry::addStatementEntry' => + compact('entry'))); + + $ret = array(); + if (!$this->verifyStatementEntry($entry)) + return array('error' => true, 'verify_data' => $entry) + $ret; + + pr(array('StatementEntry::addStatementEntry' => + array('checkpoint' => 'Pre-Save') + + compact('entry'))); + + $this->create(); + if (!$this->save($entry)) + return array('error' => true, 'save_data' => $entry) + $ret; + + $ret['statement_entry_id'] = $this->id; + return $ret + array('error' => false); + } + + /************************************************************************** ************************************************************************** ************************************************************************** @@ -354,12 +406,15 @@ OPTION 2 * - Assigns all credits to existing charges */ function assignCredits($query = null, $receipt_id = null) { - pr(array('StatementEntry::assignCredits' => compact('query'))); + pr(array('StatementEntry::assignCredits' => compact('query', 'receipt_id'))); $this->queryInit($query); + $ret = array(); + // First, find all known credits $lquery = $query; $lquery['conditions'][] = array('StatementEntry.type' => 'CREDIT'); + $lquery['order'][] = 'StatementEntry.effective_date ASC'; $credits = $this->find('all', $lquery); pr(compact('lquery', 'credits')); @@ -377,8 +432,7 @@ OPTION 2 $lquery['link'] = array('StatementEntry' => array('fields' => array()) + $lquery['link']); $lquery['conditions'][] = array('Transaction.type' => 'RECEIPT'); $lquery['fields'] = array('Transaction.id', 'Transaction.stamp', 'Transaction.amount', - //'SUM(StatementEntry.amount) AS applied_amount', - 'Transaction.amount - SUM(StatementEntry.amount) AS balance'); + 'Transaction.amount - SUM(COALESCE(StatementEntry.amount,0)) AS balance'); $lquery['group'] = 'Transaction.id HAVING balance > 0'; $anon_credits = $this->Transaction->find('all', $lquery); foreach ($anon_credits AS &$ac) { @@ -387,6 +441,20 @@ OPTION 2 } pr(compact('lquery', 'anon_credits')); + // Next, add in the credits from the newly added receipt + if (!empty($receipt_id)) { + $lquery = $query; + $lquery['conditions'][] = array('Transaction.type' => 'RECEIPT'); + $lquery['conditions'][] = array('Transaction.id' => $receipt_id); + $lquery['fields'] = array('Transaction.id', 'Transaction.stamp', 'Transaction.amount', + 'Transaction.amount AS balance'); + $receipt_credit = $this->Transaction->find('first', $lquery); + if ($receipt_credit) + $anon_credits[] = $receipt_credit; + + pr(compact('lquery', 'anon_credits')); + } + // REVISIT : 20090726 // This algorithm shouldn't be hardcoded. We need to allow // the user to specify how payments should be applied. @@ -495,10 +563,10 @@ OPTION 2 array('checkpoint' => 'New Payment Entry') + compact('payment'))); - $SE = new StatementEntry(); - $SE->create(); - if (!$SE->save($payment)) - die("UNABLE TO SAVE NEW PAYMENT ENTRY"); + $result = $this->addStatementEntry($payment); + $ret['Payment'][] = $result; + if ($result['error']) + $ret['error'] = true; // Adjust the charge balance to reflect the new payment $charge['balance'] -= $payment_amount; @@ -512,17 +580,15 @@ OPTION 2 } - // Make partially used credits are added to the used list + // Partially used credits must be added to the used list if (isset($credits[0]['applied'])) $used_credits[] = array_shift($credits); - if (isset($anon_credits[0]['applied'])) - $used_anon_credits[] = array_shift($anon_credits); pr(array('StatementEntry::assignCredits' => array('checkpoint' => 'Payments added') + compact('credits', 'used_credits', 'anon_credits', 'used_anon_credits'))); - // Finally, clean up any credits that have been used + // Clean up any explicit credits that have been used foreach ($used_credits AS $credit) { if ($credit['balance'] > 0) { pr(array('StatementEntry::assignCredits' => @@ -541,6 +607,29 @@ OPTION 2 } } + // Convert non-exhausted implicit credits to explicit ones + foreach ($anon_credits AS $credit) { + if ($credit['balance'] <= 0) + die("HOW DID EXHAUSTED ANON CREDITS GET LEFT?"); + + pr(array('StatementEntry::assignCredits' => + array('checkpoint' => 'Create Explicit Credit') + + compact('credit'))); + + $result = $this->addStatementEntry + (array('type' => 'CREDIT', + 'account_id' => $this->Account->accountReceivableAccountID(), + 'amount' => $credit['balance'], + 'effective_date' => $credit['Transaction']['stamp'], + 'transaction_id' => $credit['Transaction']['id'], + 'customer_id' => $credit['Customer']['id'], + )); + $ret['Credit'][] = $result; + if ($result['error']) + $ret['error'] = true; + } + + return $ret + array('error' => false); } @@ -565,16 +654,21 @@ OPTION 2 $rquery['link']['PaymentEntry'] = array('fields' => array()); $rquery['fields'] = array(); - $rquery['fields'][] = "SUM(StatementEntry.amount) AS total"; + $rquery['fields'][] = "StatementEntry.amount"; $rquery['fields'][] = "SUM(PaymentEntry.amount) AS reconciled"; - $rquery['fields'][] = "SUM(StatementEntry.amount - COALESCE(PaymentEntry.amount,0)) AS balance"; $rquery['conditions'][] = array('StatementEntry.type' => 'CHARGE'); - $result = $this->find('first', $rquery); - if (!isset($result[0]['balance'])) - $result[0]['balance'] = 0; - $stats['Charge'] = $result[0]; + $rquery['group'] = 'StatementEntry.id'; + $result = $this->find('all', $rquery); + $stats['Charge'] = array('total' => 0, 'reconciled' => 0); + foreach($result AS $charge) { + $stats['Charge']['total'] += $charge['StatementEntry']['amount']; + $stats['Charge']['reconciled'] += $charge[0]['reconciled']; + } + $stats['Charge']['balance'] = + $stats['Charge']['total'] - $stats['Charge']['reconciled']; + /* pr(array('StatementEntry::stats' => */ /* array('checkpoint' => 'Charges') */ /* + compact('query', 'result'))); */ diff --git a/models/tender.php b/models/tender.php index a407f64..0d1c714 100644 --- a/models/tender.php +++ b/models/tender.php @@ -13,43 +13,68 @@ class Tender extends AppModel { ); + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyTender + * - Verifies consistenty of new tender data + * (not in a pre-existing tender) + */ + function verifyTender($tender) { + pr(array("Tender::verifyTender()" + => compact('tender'))); + + if (empty($tender['tender_type_id'])) { + pr(array("Tender::verifyTender()" + => "Tender verification failed")); + return false; + } + + return true; + } + + /************************************************************************** ************************************************************************** ************************************************************************** * function: addTender - * - Adds a new tender + * - Inserts new Tender into the database */ - function addTender($data, $customer_id, $lease_id = null, $reconcile = null) { - // Create some models for convenience - $A = new Account(); + function addTender($tender) { + pr(array('Tender::addTender' => + compact('tender'))); - // Assume this will succeed - $ret = true; + $ret = array(); + if (!$this->verifyTender($tender)) + return array('error' => true) + $ret; - // Establish the key tender parameters - $tender = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1, - 'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1)); - $tender['customer_id'] = $customer_id; + // Come up with a (not necessarily unique) name for the tender. + // For checks & money orders, this will be based on the check + // number. For other types of tender, we'll just use the + // generic name of the monetary account. + // REVISIT : 20090723 + // I would like to have cash named "Cash #1234", where + // the number would correspond to either the Tender ID + // or the LedgerEntry ID. + if (empty($tender['name']) && !empty($tender['account_id'])) { + $tender['name'] = $this->LedgerEntry->Account->name($tender['account_id']); + if ($tender['account_id'] == $this->LedgerEntry->Account->checkAccountID() || + $tender['account_id'] == $this->LedgerEntry->Account->moneyOrderAccountID()) { + $tender['name'] .= ' #' . $tender['data1']; + } + } - // Create the tender entry, and reconcile the credit side - // of the double-entry (which should be A/R) as a tender. - $ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry - ($tender, - array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']), - 'credit_ledger_id' => $A->currentLedgerID($A->tenderAccountID()) - ) + $entry, - array('debit' => 'tender', - 'credit' => $reconcile) - ); - - if ($ids['error']) - $ret = false; + pr(array('Tender::addTender' => + array('checkpoint' => 'Pre-Save') + + array('Tender' => $tender))); - $tender = array_intersect_key($ids, - array('tender_id'=>1, - 'split_tender_id'=>1)); - return $ret; + $this->create(); + if (!$this->save($tender)) + return array('error' => true) + $ret; + + $ret['tender_id'] = $this->id; + return $ret + array('error' => false); } diff --git a/models/transaction.php b/models/transaction.php index ba312fd..a8be19e 100644 --- a/models/transaction.php +++ b/models/transaction.php @@ -93,6 +93,50 @@ class Transaction extends AppModel { } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: verifyTransaction + * - Verifies consistenty of new transaction data + * (not in a pre-existing transaction) + */ + function verifyTransaction($transaction, $entries) { + pr(array("Transaction::verifyTransaction()" + => compact('transaction', 'entries', 'customer_id', 'lease_id'))); + + // Verify required Transaction data is present + if (empty($transaction['type']) || + empty($transaction['account_id']) || + empty($transaction['crdr']) || + (in_array($transaction['type'], array('INVOICE', 'RECEIPT')) + && empty($transaction['customer_id'])) || + (in_array($transaction['type'], array('INVOICE')) + && empty($transaction['lease_id'])) + ) { + pr(array("Transaction::verifyTransaction()" + => "Transaction verification failed")); + return false; + } + + // Verify all entries + foreach ($entries AS $entry) { + extract($entry); + if (!$this->LedgerEntry->DoubleEntry->verifyDoubleEntry($le1, $le2, $le1_tender)) { + pr(array("Transaction::verifyTransaction()" + => "Double Entry verification failed")); + return false; + } + if (!$this->StatementEntry->verifyStatementEntry($se)) { + pr(array("Transaction::verifyTransaction()" + => "Statement Entry verification failed")); + return false; + } + } + + return true; + } + + /************************************************************************** ************************************************************************** ************************************************************************** @@ -152,7 +196,11 @@ class Transaction extends AppModel { */ function addTransaction($data, $customer_id, $lease_id = null) { - pr(compact('data', 'customer_id', 'lease_id')); + pr(array("Transaction::addTransaction()" + => compact('data', 'customer_id', 'lease_id'))); + + if (!isset($data['Transaction']) || !isset($data['Entry'])) + return array('error' => true); // Automatically figure out the customer if we have the lease if (!empty($lease_id) && empty($customer_id)) { @@ -162,86 +210,74 @@ class Transaction extends AppModel { $customer_id = $lease['Lease']['customer_id']; } - if (!isset($data['Transaction']) || !isset($data['Entry'])) - return array('error' => true); - - // Keep only relevent items from Transaction - $transaction = array_intersect_key($data['Transaction'], - array_flip(array('type', 'account_id', 'crdr', - 'stamp', 'comment', - 'LedgerEntry'))); - - // Verify required Transaction data is present - if (empty($transaction['type']) || - empty($transaction['account_id']) || - empty($transaction['crdr']) || - (in_array($transaction['type'], array('INVOICE', 'RECEIPT')) - && !$customer_id) || - (in_array($transaction['type'], array('INVOICE')) - && !$lease_id) - ) { - pr("Transaction verification failed"); - return array('error' => true); - } - - // Verify each Entry, and calculation the transaction total - $transaction['amount'] = 0; - foreach ($data['Entry'] AS &$entry) { - if (empty($entry['type']) || - empty($entry['account_id']) || - empty($entry['crdr']) || - empty($entry['amount']) - ) { - pr("Entry verification failed"); - return array('error' => true); - } - - // Keep only relevent items from Entry - $entry = - array_intersect_key($entry, - array_flip(array('type', 'account_id', 'crdr', 'amount', - 'effective_date', 'through_date', 'due_date', - 'comment', - 'ledger_entry_comment', - 'statement_entry_comment', - 'Tender'))); - - if (isset($entry['Tender'])) { - - // Verify required Tender data is present - if (empty($entry['Tender']['tender_type_id'])) { - pr("Tender verification failed"); - return array('error' => true); - } - - // Keep only relevent items - $entry['Tender'] = - array_intersect_key($entry['Tender'], - array_flip(array('tender_type_id', 'name', - 'data1', 'data2', 'data3', 'data4', - 'comment'))); - } - - // Try to figure out where 'comment' should go - if (empty($entry['ledger_entry_comment']) && - !empty($entry['comment']) && - $entry['type'] === 'PAYMENT') - $entry['ledger_entry_comment'] = $entry['comment']; - if (empty($entry['statement_entry_comment']) && - !empty($entry['comment']) && - $entry['type'] === 'CHARGE') - $entry['statement_entry_comment'] = $entry['comment']; - - // OK, add entry amount into the transaction total - $transaction['amount'] += $entry['amount']; - } - // Set the customer based on caller request, and set // ledger ID as the current ledger of the specified account + $transaction = $data['Transaction']; $transaction['customer_id'] = $customer_id; + $transaction['lease_id'] = $lease_id; $transaction['ledger_id'] = $this->Account->currentLedgerID($transaction['account_id']); + + // Break each entry out of the combined statement/ledger entry + // and into individual entries appropriate for saving. While + // we're at it, calculate the transaction total as well. + $transaction['amount'] = 0; + foreach ($data['Entry'] AS &$entry) { + // Add entry amount into the transaction total + $transaction['amount'] += $entry['amount']; + + $entry['customer_id'] = $transaction['customer_id']; + $entry['lease_id'] = $transaction['lease_id']; + $entry['ledger_id'] = + $this->Account->currentLedgerID($entry['account_id']); + + // Set up our comments, possibly using the default 'comment' field + if (empty($entry['ledger_entry_comment'])) { + if (!empty($entry['comment']) && $entry['type'] === 'PAYMENT') + $entry['ledger_entry_comment'] = $entry['comment']; + else + $entry['ledger_entry_comment'] = null; + } + if (empty($entry['statement_entry_comment'])) { + if (!empty($entry['comment']) && $entry['type'] === 'CHARGE') + $entry['statement_entry_comment'] = $entry['comment']; + else + $entry['statement_entry_comment'] = null; + } + + // Create one half of the Double Ledger Entry (and the Tender) + $le1 = + array_intersect_key($entry, + array_flip(array('account_id', 'crdr', 'amount'))); + $le1['comment'] = $entry['ledger_entry_comment']; + $le1_tender = isset($entry['Tender']) ? $entry['Tender'] : null; + + // Create the second half of the Double Ledger Entry + $le2 = + array_intersect_key($transaction, + array_flip(array('account_id', 'crdr'))) + + array_intersect_key($entry, + array_flip(array('amount'))); + + // Create a statement entry + $se = + array_intersect_key($transaction, + array_flip(array('customer_id', 'lease_id'))) + + array_intersect_key($entry, + array_flip(array('type', 'account_id', 'amount', + 'effective_date', 'through_date', 'due_date'))); + $se['comment'] = $entry['statement_entry_comment']; + + // Replace combined entry with our new individual entries + $entry = compact('le1', 'le1_tender', 'le2', 'se'); + } + + // Move forward, verifying and saving everything. + $ret = array(); + if (!$this->verifyTransaction($transaction, $data['Entry'], $customer_id, $lease_id)) + return array('error' => true) + $ret; + pr(array('Transaction::addTransaction' => array('checkpoint' => 'Pre Transaction Save') + compact('transaction'))); @@ -249,104 +285,39 @@ class Transaction extends AppModel { // Save transaction to the database $this->create(); if (!$this->save($transaction)) - return array('error' => true); + return array('error' => true) + $ret; // Set up our return ids array - $ret = array('transaction_id' => $this->id, - 'Entry' => array(), - 'error' => false); + $ret['transaction_id'] = $this->id; + $ret['entries'] = array(); + $ret['error'] = false; // Go through the entered charges foreach ($data['Entry'] AS $e_index => &$entry) { + extract($entry); + $le1['transaction_id'] = $ret['transaction_id']; + $le2['transaction_id'] = $ret['transaction_id']; + $se['transaction_id'] = $ret['transaction_id']; - $entry['transaction_id'] = $this->id; - $entry['customer_id'] = $customer_id; - $entry['lease_id'] = $lease_id; - $entry['ledger_id'] = - $this->Account->currentLedgerID($entry['account_id']); - - // Use ledger_entry_comment as the comment - if (!empty($entry['ledger_entry_comment'])) - $entry['comment'] = $entry['ledger_entry_comment']; - else - $entry['comment'] = null; - - pr(array('Transaction::addTransaction' => - array('checkpoint' => 'Pre Ledger Entry Save') - + array('entry' => array_diff_key($entry, array('Tender'=>1))))); - - $LE = new LedgerEntry(); - $LE->create(); - if ($LE->save($entry)) - $ret['Entry'][$e_index]['ledger_entry_id'] = $LE->id; - else { - $ret['Entry'][$e_index]['ledger_entry_id'] = null; + $result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender); + $ret['entries'][$e_index]['DoubleEntry'] = $result; + if ($result['error']) { $ret['error'] = true; continue; } - if (isset($entry['Tender'])) { - // Come up with a non-unique name for the legal tender. - // For checks & money orders, this will be based on the - // check number. For cash, we'll just use the generic - // name of the account. - // REVISIT : 20090723 - // I would like to have cash named "Cash #1234", where - // the number would correspond to either the Tender ID - // or the LedgerEntry ID. - if (empty($entry['Tender']['name'])) { - $entry['Tender']['name'] = $this->Account->name($entry['account_id']); - if ($entry['account_id'] == $this->Account->checkAccountID() || - $entry['account_id'] == $this->Account->moneyOrderAccountID()) { - $entry['Tender']['name'] .= ' #' . $entry['Tender']['data1']; - } - } - - $entry['Tender']['ledger_entry_id'] = $LE->id; - - pr(array('Transaction::addTransaction' => - array('checkpoint' => 'Pre Tender Save') - + array('Tender' => $entry['Tender']))); - - $Tender = new Tender(); - $Tender->create(); - if ($Tender->save($entry['Tender'])) - $ret['Entry'][$e_index]['tender_id'] = $Tender->id; - else { - $ret['Entry'][$e_index]['tender_id'] = null; - $ret['error'] = true; - continue; - } - } - - if ($entry['type'] === 'CHARGE') { + if ($se['type'] === 'CHARGE') { // CHARGES need to be added as statement entries. // PAYMENTS will be added later after reconciliation - - $SE = new StatementEntry(); - $SE->create(); - - // Use statement_entry_comment as the comment - if (!empty($entry['statement_entry_comment'])) - $entry['comment'] = $entry['statement_entry_comment']; - else - $entry['comment'] = null; - - pr(array('Transaction::addTransaction' => - array('checkpoint' => 'Pre Statement Entry Save') - + array('entry' => array_diff_key($entry, array('Tender'=>1))))); - - if ($SE->save($entry)) - $ret['Entry'][$e_index]['statement_entry_id'] = $SE->id; - else { - $ret['Entry'][$e_index]['statement_entry_id'] = null; + $result = $this->StatementEntry->addStatementEntry($se); + $ret['entries'][$e_index]['StatementEntry'] = $result; + if ($result['error']) { $ret['error'] = true; continue; } } } - // REVISIT : 20090723 // Now that we have new entries, WE MUST RECONCILE // THE CHARGES TO CUSTOMER ACCOUNT BALANCE! This @@ -354,6 +325,19 @@ class Transaction extends AppModel { // the only way to make use of a positive customer // balance if new charges have been entered. + if (!$ret['error']) { + $result = $this->StatementEntry->assignCredits + (array('link' => array('Customer'), + 'conditions' => array('Customer.id' => $customer_id)), + ($transaction['type'] === 'RECEIPT' + ? $ret['transaction_id'] + : null)); + + $ret['assigned'] = $result; + if ($result['error']) + $ret['error'] = true; + } + pr(array('Transaction::addTransaction' => array('return' => $ret)));