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(); $invoice['customer_id'] = $customer_id; $invoice['lease_id'] = $lease_id; // Go through the statement entries and flag as charges foreach ($data['Entry'] AS &$entry) { $entry['type'] = 'CHARGE'; $entry['crdr'] = 'CREDIT'; } $ids = $this->addTransaction($data); 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 a receipt $receipt =& $data['Transaction']; $receipt['type'] = 'RECEIPT'; $receipt['crdr'] = 'CREDIT'; $receipt['account_id'] = $this->Account->accountReceivableAccountID(); $receipt['customer_id'] = $customer_id; $receipt['lease_id'] = $lease_id; // Go through the statement entries and flag as payments foreach ($data['Entry'] AS &$entry) { $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); if (isset($ids['transaction_id'])) $ids['receipt_id'] = $ids['transaction_id']; return $ids; } /************************************************************************** ************************************************************************** ************************************************************************** * function: addDeposit * - Adds a new bank deposit */ function addDeposit($data, $account_id) { // Establish the transaction as a deposit $deposit =& $data['Transaction']; $deposit['type'] = 'DEPOSIT'; $deposit['crdr'] = 'DEBIT'; $deposit['account_id'] = $account_id; $deposit['customer_id'] = null; $deposit['lease_id'] = null; // Go through the statement entries and flag as credits foreach ($data['Entry'] AS &$entry) { $entry['crdr'] = 'CREDIT'; } // For some reason, $data is being modified by the // addTransaction call, even though we're not passing by // reference. Not all items in $data['Entry'] are being // stomped on... only the last one in the list :-/ // Save off all the tender ids now, before calling // addTransaction, to make sure we don't lose any. $tender_ids = array_map(create_function('$item', /* 'if (!isset($item))' . */ /* ' pr("NO ITEM");' . */ /* 'if (!isset($item["tender_id"]))' . */ /* ' pr(array("BAD ITEM" => compact("item")));' . */ 'return $item["tender_id"];'), $data['Entry']); $ids = $this->addTransaction($data); if (isset($ids['transaction_id'])) $ids['deposit_id'] = $ids['transaction_id']; if (!empty($ids['deposit_id'])) { $this->LedgerEntry->Tender->updateAll (array('Tender.deposit_transaction_id' => $ids['deposit_id']), array('Tender.id' => $tender_ids) ); } 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 ($transaction['type'] == 'INVOICE' && !$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) { /* pr(array("Transaction::addTransaction()" */ /* => compact('data', 'customer_id', 'lease_id'))); */ // Verify that we have a transaction and entries if (empty($data['Transaction']) || empty($data['Entry'])) return array('error' => true); // Extract the transaction to a local variable $transaction = $data['Transaction']; // set ledger ID as the current ledger of the specified account $transaction['ledger_id'] = $this->Account->currentLedgerID($transaction['account_id']); // Automatically figure out the customer if we have the lease if (!empty($transaction['lease_id']) && empty($transaction['customer_id'])) { $L = new Lease(); $L->recursive = -1; $lease = $L->read(null, $transaction['lease_id']); $transaction['customer_id'] = $lease['Lease']['customer_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 ($transaction['type'] != 'INVOICE' && !empty($entry['comment'])) $entry['ledger_entry_comment'] = $entry['comment']; else $entry['ledger_entry_comment'] = null; } if (empty($entry['statement_entry_comment'])) { if ($transaction['type'] == 'INVOICE' && !empty($entry['comment'])) $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'))); if ($transaction['type'] == 'INVOICE') { // Create a statement entry // (PAYMENTS will have statement entries created below, when // assigning credits, and DEPOSITS don't have statement entries) $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'])) 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'] = $le2['transaction_id'] = $ret['transaction_id']; $le1_tender['customer_id'] = $transaction['customer_id']; $result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender); $ret['entries'][$e_index]['DoubleEntry'] = $result; if ($result['error']) { $ret['error'] = true; continue; } if (isset($se)) { $se['transaction_id'] = $ret['transaction_id']; $result = $this->StatementEntry->addStatementEntry($se); $ret['entries'][$e_index]['StatementEntry'] = $result; if ($result['error']) { $ret['error'] = true; continue; } } } if (($transaction['type'] == 'INVOICE' || $transaction['type'] == 'RECEIPT') && !$ret['error']) { $result = $this->StatementEntry->assignCredits (array('link' => array('Customer'), 'conditions' => array('Customer.id' => $transaction['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; } } ?>