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: 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(compact('data', 'customer_id', 'lease_id')); // 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']; } 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['customer_id'] = $customer_id; $transaction['ledger_id'] = $this->Account->currentLedgerID($transaction['account_id']); 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); // Set up our return ids array $ret = array('transaction_id' => $this->id, 'Entry' => array(), 'error' => false); // Go through the entered charges foreach ($data['Entry'] AS $e_index => &$entry) { $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; $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') { // 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; $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. pr(array('Transaction::addTransaction' => array('return' => $ret))); return $ret; } /************************************************************************** ************************************************************************** ************************************************************************** * function: stats * - Returns summary data from the requested transaction */ function stats($id = null, $query = 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') { $squery['fields'] = array_merge($squery['fields'], $this->LedgerEntry->debitCreditFields(true, false)); } 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; } } ?>