array( 'className' => 'StatementEntry', 'conditions' => array('Charge.type' => 'CHARGE') ), 'Payment' => array( 'className' => 'StatementEntry', 'conditions' => array('Payment.type' => 'PAYMENT') ), 'Debit' => array( 'className' => 'LedgerEntry', 'conditions' => array('Debit.crdr' => 'DEBIT') ), 'Credit' => array( 'className' => 'LedgerEntry', 'conditions' => array('Credit.crdr' => 'CREDIT') ), ); /************************************************************************** ************************************************************************** ************************************************************************** * function: addInvoice * - Adds a new invoice invoice */ function addInvoice($data, $customer_id, $lease_id = null) { // Establish the transaction as an invoice $invoice =& $data['Transaction']; $invoice['type'] = 'INVOICE'; $invoice['crdr'] = 'DEBIT'; $invoice['account_id'] = $this->Account->accountReceivableAccountID(); // Go through the statement entries and flag as charges foreach ($data['Entry'] AS &$entry) { $entry['type'] = 'CHARGE'; $entry['crdr'] = 'CREDIT'; } $ids = $this->addTransaction($data, $customer_id, $lease_id); if (isset($ids['transaction_id'])) $ids['invoice_id'] = $ids['transaction_id']; return $ids; } /************************************************************************** ************************************************************************** ************************************************************************** * function: addReceipt * - Adds a new receipt */ function addReceipt($data, $customer_id, $lease_id = null) { // Establish the transaction as an receipt $receipt =& $data['Transaction']; $receipt['type'] = 'RECEIPT'; $receipt['crdr'] = 'CREDIT'; $receipt['account_id'] = $this->Account->accountReceivableAccountID(); // Go through the statement entries and flag as payments foreach ($data['Entry'] AS &$entry) { $entry['type'] = 'PAYMENT'; $entry['crdr'] = 'DEBIT'; if (isset($entry['Tender']['tender_type_id'])) { $entry['account_id'] = $this->LedgerEntry->Tender->TenderType-> accountID($entry['Tender']['tender_type_id']); } } $ids = $this->addTransaction($data, $customer_id, $lease_id); if (isset($ids['transaction_id'])) $ids['receipt_id'] = $ids['transaction_id']; return $ids; } /************************************************************************** ************************************************************************** ************************************************************************** * 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; } /************************************************************************** ************************************************************************** ************************************************************************** * function: addTransaction * - Adds a new transaction, and the appropriate ledger and statement * entries, as layed out in the $data['Entry'] array. The array is * overloaded, since it is used to create both ledger _and_ statement * entries. * * $data * - Transaction * - [MANDATORY] * - type (INVOICE, RECEIPT) * - account_id * - crdr * - [OPTIONAL] * - stamp * (default: NOW) * - comment * - [AUTOMATICALLY SET] (if set, these items will be overwritten) * - id * - amount * - customer_id * - ledger_id * * - Entry (array) * - [MANDATORY] * - type (CHARGE, PAYMENT) * - account_id * - crdr * - amount * - [OPTIONAL] * - effective_date * - through_date * - due_date * - comment (used for statement or ledger entry, based on context) * - ledger_entry_comment * - statement_entry_comment * - Tender * - [MANDATORY] * - tender_type_id * - [OPTIONAL] * - name * (default: Entry Account Name & data1) * - data1, data2, data3, data4 * - comment * - [AUTOMATICALLY SET] (if set, these items will be overwritten) * - id * - ledger_entry_id * - deposit_transaction_id * - nsf_transaction_id * - [AUTOMATICALLY SET] (if set, these items will be overwritten) * - id * - transaction_id * - ledger_id * */ function addTransaction($data, $customer_id, $lease_id = null) { 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)) { $L = new Lease(); $L->recursive = -1; $lease = $L->read(null, $lease_id); $customer_id = $lease['Lease']['customer_id']; } // 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'))); // Save transaction to the database $this->create(); if (!$this->save($transaction)) return array('error' => true) + $ret; // Set up our return ids array $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']; $result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender); $ret['entries'][$e_index]['DoubleEntry'] = $result; if ($result['error']) { $ret['error'] = true; continue; } if ($se['type'] === 'CHARGE') { // CHARGES need to be added as statement entries. // PAYMENTS will be added later after reconciliation $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 // is the only way "payments" are generated, and // 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))); return $ret; } /************************************************************************** ************************************************************************** ************************************************************************** * function: stats * - Returns summary data from the requested transaction */ function stats($id = null, $query = null, $balance_account_id = null) { //pr(array('Transaction::stats' => compact('id', 'query'))); $this->queryInit($query); unset($query['group']); if (isset($id)) { $query['conditions'][] = array('Transaction.id' => $id); $query['group'] = 'Transaction.id'; } else // CakePHP seems to automagically add in our ID as a part // of the query conditions, but only on a 'first' query, // not an 'all'. I suppose this is helpful :-/ unset($this->id); if (empty($query['fields'])) $query['fields'] = array(); $stats = array(); foreach (array_keys($this->hasMany) AS $table) { $squery = $query; $squery['link'][$table] = array('fields' => array()); if ($table == 'LedgerEntry') { if (isset($balance_account_id)) { $squery['link']['LedgerEntry']['Account'] = array('fields' => array()); $squery['conditions'][] = array("Account.id" => $balance_account_id); } $squery['fields'] = array_merge($squery['fields'], $this->LedgerEntry->debitCreditFields(true, $balance_account_id != null)); } elseif ($table == 'StatementEntry') { $squery['fields'] = array_merge($squery['fields'], $this->StatementEntry->chargePaymentFields(true)); } else { $squery['fields'][] = "SUM({$table}.amount) AS total"; $squery['fields'][] = "COUNT({$table}.id) AS entries"; } $stats[$table] = $this->find('first', $squery); // REVISIT : 20090724 // [0][0] is for when we do an 'all' query. This can // be removed at some point, but I'm keeping it while // toggling between 'all' and 'first' (testing). if (isset($stats[$table][0][0])) $stats[$table] += $stats[$table][0][0]; else $stats[$table] += $stats[$table][0]; unset($stats[$table][0]); } //pr(array('Transaction::stats' => array('return' => compact('stats')))); return $stats; } } ?>