diff --git a/models/transaction.php b/models/transaction.php index dc4d4e1..db74707 100644 --- a/models/transaction.php +++ b/models/transaction.php @@ -20,42 +20,23 @@ class Transaction extends AppModel { */ function addInvoice($data, $customer_id, $lease_id = null) { - // Create some models for convenience - $A = new Account(); - - //pr(compact('data', 'customer_id', 'lease_id')); - - // Assume this will succeed - $ret = true; - - // Establish the key invoice parameters - $invoice = array_intersect_key($data, array('Invoice'=>1)); + // Establish the transaction as an invoice + $invoice =& $data['Transaction']; $invoice['type'] = 'INVOICE'; + $invoice['crdr'] = 'DEBIT'; + $invoice['account_id'] = $this->Account->accountReceivableAccountID(); - // Determine the total charges on the invoice - $invoice['amount'] = 0; - foreach ($data['LedgerEntry'] AS $entry) - $invoice['amount'] += $entry['amount']; - - // Go through the entered charges - foreach ($data['LedgerEntry'] AS $entry) { - //pr(compact('entry')); - // Create the receipt entry, and reconcile the credit side - // of the double-entry (which should be A/R) as a payment. - $ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry - ($invoice, - array('debit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()), - 'credit_ledger_id' => $A->currentLedgerID($entry['account_id']) - ) + $entry - ); - - if ($ids['error']) - $ret = false; - - $invoice = array_intersect_key($ids, array('invoice_id'=>1)); + // Go through the statement entries and flag as charges + foreach ($data['Entry'] AS &$entry) { + $entry['type'] = 'CHARGE'; + $entry['crdr'] = 'CREDIT'; } - return $ret; + $ids = $this->addTransaction($data, $customer_id, $lease_id); + if (isset($ids['transaction_id'])) + $ids['invoice_id'] = $ids['transaction_id']; + + return $ids; } @@ -67,43 +48,293 @@ class Transaction extends AppModel { */ function addReceipt($data, $customer_id, $lease_id = null) { - // Create some models for convenience - $A = new Account(); - - // Assume this will succeed - $ret = true; - - // Establish the key receipt parameters - $receipt = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1, - 'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1)); + // Establish the transaction as an receipt + $receipt =& $data['Transaction']; $receipt['type'] = 'RECEIPT'; + $receipt['crdr'] = 'CREDIT'; + $receipt['account_id'] = $this->Account->accountReceivableAccountID(); - // Determine the total charges on the receipt - $receipt['amount'] = 0; - foreach ($data['LedgerEntry'] AS $entry) - $receipt['amount'] += $entry['amount']; + // Go through the statement entries and flag as payments + foreach ($data['Entry'] AS &$entry) { + $entry['type'] = 'PAYMENT'; + $entry['crdr'] = 'DEBIT'; + } + + $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] + * - type (CASH, CHECK, etc) + * - [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']) || + (isset($entry['Tender']) && + (empty($entry['Tender']['type']) + )) + ) { + 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']['type'])) { + pr("Tender verification failed"); + return array('error' => true); + } + + // Keep only relevent items + $entry['Tender'] = + array_intersect_key($entry['Tender'], + array_flip(array('type', '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['LedgerEntry'] AS $entry) { - // Create the receipt entry, and reconcile the credit side - // of the double-entry (which should be A/R) as a receipt. - $ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry - ($receipt, - array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']), - 'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()) - ) + $entry, - array('debit' => 'receipt', - 'credit' => $reconcile) - ); - - if ($ids['error']) - $ret = false; + foreach ($data['Entry'] AS $e_index => &$entry) { - $receipt = array_intersect_key($ids, - array('receipt_id'=>1, - 'split_receipt_id'=>1)); + $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; } diff --git a/views/leases/invoice.ctp b/views/leases/invoice.ctp index 85829a6..0e530d6 100644 --- a/views/leases/invoice.ctp +++ b/views/leases/invoice.ctp @@ -154,7 +154,7 @@ function addChargeSource(flash) { ($this->element('form_table', array('class' => "item invoice ledger-entry entry", //'with_name_after' => ':', - 'field_prefix' => 'LedgerEntry.%{id}', + 'field_prefix' => 'Entry.%{id}', 'fields' => array ("account_id" => array('name' => 'Account', 'opts' => @@ -164,11 +164,11 @@ function addChargeSource(flash) { ), "effective_date" => array('opts' => array('type' => 'text'), - 'between' => 'BOM', + 'between' => 'BOM', ), "through_date" => array('opts' => array('type' => 'text'), - 'between' => 'EOM', + 'between' => 'EOM', ), "amount" => true, "comment" => array('opts' => array('size' => 50)), @@ -179,14 +179,14 @@ function addChargeSource(flash) { '' ); - $("#LedgerEntry"+id+"EffectiveDate") + $("#Entry"+id+"EffectiveDate") .attr('autocomplete', 'off') .datepicker({ constrainInput: true, numberOfMonths: [1, 1], showCurrentAtPos: 0, dateFormat: 'mm/dd/yy' }); - $("#LedgerEntry"+id+"ThroughDate") + $("#Entry"+id+"ThroughDate") .attr('autocomplete', 'off') .datepicker({ constrainInput: true, numberOfMonths: [1, 1],