diff --git a/app_model.php b/app_model.php index 14fff2d..f2f0197 100644 --- a/app_model.php +++ b/app_model.php @@ -137,7 +137,16 @@ class AppModel extends Model { */ function dateFormatBeforeSave($dateString) { - return date('Y-m-d', strtotime($dateString)); +/* $time = ''; */ +/* if (preg_match('/(\d+(:\d+))/', $dateString, $match)) */ +/* $time = ' '.$match[1]; */ +/* $dateString = preg_replace('/(\d+(:\d+))/', '', $dateString); */ +/* return date('Y-m-d', strtotime($dateString)) . $time; */ + + if (preg_match('/:/', $dateString)) + return date('Y-m-d H:i:s', strtotime($dateString)); + else + return date('Y-m-d', strtotime($dateString)); } } diff --git a/controllers/accounts_controller.php b/controllers/accounts_controller.php index c35989a..d957f10 100644 --- a/controllers/accounts_controller.php +++ b/controllers/accounts_controller.php @@ -119,6 +119,25 @@ class AccountsController extends AppController { } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * action: newledger + * - Close the current account ledger and create a new one, + * carrying forward any balance if necessary. + */ + + function newledger($id = null) { + if (!$this->Account->closeCurrentLedger($id)) { + $this->Session->setFlash(__('Unable to create new Ledger.', true)); + } + if ($id) + $this->redirect(array('action'=>'view', $id)); + else + $this->redirect(array('action'=>'index')); + } + + /************************************************************************** ************************************************************************** ************************************************************************** @@ -160,8 +179,14 @@ class AccountsController extends AppController { $stats = $this->Account->stats($id, true); $stats = $stats['Ledger']; + $this->sidemenu_links[] = + array('name' => 'Operations', 'header' => true); + $this->sidemenu_links[] = + array('name' => 'New Ledger', 'url' => array('action' => 'newledger', $id)); + // Prepare to render $title = 'Account: ' . $account['Account']['name']; $this->set(compact('account', 'title', 'stats')); } + } diff --git a/controllers/transactions_controller.php b/controllers/transactions_controller.php index e4a3f1f..8f182cf 100644 --- a/controllers/transactions_controller.php +++ b/controllers/transactions_controller.php @@ -103,186 +103,204 @@ class TransactionsController extends AppController { */ function postReceipt() { - $this->autoRender = false; - if (!$this->RequestHandler->isPost()) { echo('

THIS IS NOT A POST FOR SOME REASON

'); return; } - //pr(array('thisdata' => $this->data)); - if (isset($this->data['customer_id'])) { - $C = new Customer(); - $C->recursive = -1; - $customer = $C->find - ('first', - array('contain' => array('Account.CurrentLedger.id'), - 'fields' => false, - 'conditions' => array('Customer.id', $this->data['customer_id']), - )); - $ledger_id = $customer['Account']['CurrentLedger']['id']; - } - else { - // Payment by Unit, Lease, etc - $ledger_id = 0; + $this->layout = null; + $this->autoLayout = false; + $this->autoRender = false; + Configure::write('debug', '0'); + + // Sanitize the transaction data + if (!$this->data['Transaction']['comment']) + $this->data['Transaction']['comment'] = null; + + if(empty($this->data['Transaction']['stamp'])) { + die("Time/Date not valid"); } + pr($this->data['Transaction']); - $amount = 0; - foreach ($this->data['LedgerEntry'] AS &$entry) { - $reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'], - 'credit', - $entry['amount']); - pr(compact('entry', 'reconciled')); - - foreach ($reconciled['debit']['entry'] AS $rec) { - $entry['DebitReconciliationLedgerEntry'] = - array('amount' => $rec['applied'], - //'debit_ledger_entry_id' - 'credit_ledger_entry_id' => $rec['id'] - ); - } - - $amount += isset($entry['amount']) ? $entry['amount'] : 0; - $entry['debit_ledger_id'] = 6; // Cash/Payments - $entry['credit_ledger_id'] = $ledger_id; - } - - - pr($this->data); - $T = new Transaction(); - $T->create(); - if ($T->saveAll($this->data, - array( - 'validate' => false, - //'fieldList' => array(), - //'callbacks' => true, - ))) { - $tid = $T->id; - $this->Session->setFlash(__("New Transaction Created ($tid)!", true)); - //$this->redirect(array('action'=>'view', $mid)); - } - else { - $this->autoRender = false; - pr(array('checkpoint' => "saveAll failed")); - } - pr($T->data); - + // Create some models for convenience + $A = new Account(); $C = new Customer(); - $LE = new LedgerEntry(); -/* $reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'], */ -/* 'credit', */ -/* $amount); */ -/* pr(compact('amount', 'unreconciled', 'reconciled')); */ -/* return; */ + // Create a transaction for the receipt + $receipt_transaction = new Transaction(); + $receipt_transaction->create(); + if (!$receipt_transaction->save($this->data['Transaction'], + array('validate' => false, + ))) { + pr(array('checkpoint' => "receipt transaction save failed")); + die("Unknown Database Failure"); + } + pr("New Transaction Created ({$receipt_transaction->id})!"); + $receipt_transaction->read(); + pr($receipt_transaction->data); + + // Create a transaction for the splits + $split_transaction = new Transaction(); + $split_transaction->create(); + if (!$split_transaction->save($this->data['Transaction'], + array('validate' => false, + ))) { + pr(array('checkpoint' => "split transaction save failed")); + die("Unknown Database Failure"); + } + pr("New Transaction Created ({$split_transaction->id})!"); + $split_transaction->read(); + pr($split_transaction->data); + + // Go through the entered payments foreach ($this->data['LedgerEntry'] AS &$entry) { + + // Get the Monetary Source squared away + if ($entry['monetary_type_name'] === 'Cash') { + // No distinguishing features of Cash, just + // use the shared monetary source + $entry['monetary_source_id'] = + $this->Transaction->LedgerEntry->MonetarySource->nameToID('Cash'); + unset($entry['MonetarySource']); + } + else { + // The monetary source needs to be unique + // Create a new one dedicated to this entry + $entry['MonetarySource']['monetary_type_id'] = + $this->Transaction->LedgerEntry->MonetarySource->MonetaryType + ->nameToID($entry['monetary_type_name']); + + $entry['MonetarySource']['name'] = + $this->Transaction->LedgerEntry->MonetarySource->MonetaryType + ->nameToID($entry['monetary_type_name']); + + // Give it a fancy name based on the check number + $entry['MonetarySource']['name'] = $entry['monetary_type_name']; + if ($entry['monetary_type_name'] === 'Check' || + $entry['monetary_type_name'] === 'Money Order') { + $entry['MonetarySource']['name'] .= + ' #' . $entry['MonetarySource']['data1']; + } + } + + + // This entry of physical money is part of the receipt transaction + // debit: Cash/Check/Etc credit: Receipt + $entry['transaction_id'] = $receipt_transaction->id; + + // Receipt must debit the "money" asset (bank, cash, check, etc)... + $entry['debit_ledger_id'] + = $A->currentLedgerID($A->nameToID($entry['monetary_type_name'])); + + // ...and credit the Receipt ledger + $entry['credit_ledger_id'] + = $A->currentLedgerID($A->receiptAccountID()); + + $entry['customer_id'] + = $this->data['customer_id']; + + // Create it + $receipt_entry = new LedgerEntry(); + $receipt_entry->create(); + if (!$receipt_entry->saveAll($entry, + array('validate' => false, + ))) { + pr(array('checkpoint' => "receipt entry saveAll failed")); + die("Unknown Database Failure"); + } + pr("New Receipt LedgerEntry Created ({$receipt_entry->id})!"); + $receipt_entry->read(); + pr($receipt_entry->data); + $reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'], 'credit', $entry['amount']); pr(compact('entry', 'reconciled')); - continue; - foreach ($reconciled['debit']['entry'] AS $rec) { - $data = array('LedgerEntry' => - array('DebitReconciliationLedgerEntry' => - array('amount' => $rec['applied'], - //'debit_ledger_entry_id' - 'credit_ledger_entry_id' => $rec['id'] - ), - ), - ); - //'DebitReconciliationLedgerEntry' => array( - //pr(compact('amount', 'unreconciled', 'reconciled')); + foreach (array_merge($reconciled['debit']['entry'], array + (array('id' => null, + 'applied' => $reconciled['debit']['unapplied'], + 'customer_id' => $this->data['customer_id'], + 'lease_id' => null))) AS $rec) { + + pr(array('checkpoint' => "Handle Reconciled Entry", + compact('rec'), + )); + if (!$rec['applied']) + continue; + + // Create an entry to handle the splitting of the funds ("Payment") + // Payment must debit the Receipt ledger, and credit the A/R ledger + // debit: Receipt credit: A/R + $split_entry_data = array + ('debit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()), + 'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()), + 'transaction_id' => $split_transaction->id, + 'amount' => $rec['applied'], + 'lease_id' => $rec['lease_id'], + 'customer_id' => $rec['customer_id'], + ); + + // Create a new split entry from the data + $split_entry = new LedgerEntry(); + $split_entry->create(); + if (!$split_entry->save($split_entry_data, + array('validate' => false, + ))) { + pr(array('checkpoint' => "split entry save failed")); + die("Unknown Database Failure"); + } + pr("New Split LedgerEntry Created ({$split_entry->id})!"); + $split_entry->read(); + pr($split_entry->data); + + // Reconcile the Receipt account. Our two entries look like: + // debit: Cash/Check/Etc credit: Receipt + // debit: Receipt credit: A/R + // Since this is from the perspective of the Receipt account, + // the debit entry is the Receipt<->A/R, and the credit + // entry is the actual receipt ledger entry. + $R = new Reconciliation(); + $R->create(); + if (!$R->save(array('debit_ledger_entry_id' => $split_entry->id, + 'credit_ledger_entry_id' => $receipt_entry->id, + 'amount' => $rec['applied']), + array('validate' => false, + ))) { + pr(array('checkpoint' => "receipt reconcile save failed")); + die("Unknown Database Failure"); + } + pr("New Receipt Reconciliation Created ({$R->id})!"); + $R->read(); + pr($R->data); + + // Only reconcile the A/R account if we have an entry + // to reconcile with, otherwise, just go on. + if (!$rec['id']) + continue; + + // Reconcile the A/R account. Our two entries look like: + // debit: Receipt credit: A/R + // debit: A/R credit: Invoice + // Since this is from the perspective of the A/R account, + // the debit entry is the Invoice<->A/R, and the credit + // entry is the Receipt<->A/R. + $R = new Reconciliation(); + $R->create(); + if (!$R->save(array('debit_ledger_entry_id' => $rec['id'], + 'credit_ledger_entry_id' => $split_entry->id, + 'amount' => $rec['applied']), + array('validate' => false, + ))) { + pr(array('checkpoint' => "split reconcile save failed")); + die("Unknown Database Failure"); + } + pr("New Split Reconciliation Created ({$R->id})!"); + $R->read(); + pr($R->data); } } - } - function saveTest() { - $data = - array( -/* 'Customer' => array */ -/* ('id' => 7, */ -/* ), */ - - 'LedgerEntry' => array - ( - '0' => array( - 'amount' => 100, - 'debit_ledger_id' => 1, - 'credit_ledger_id' => 2, - 'MonetarySource' => array('name' => 'testmoney', 'monetary_type_id' => 2), - ), - '1' => array( - 'amount' => 101, - 'debit_ledger_id' => 1, - 'credit_ledger_id' => 2, - 'MonetarySource' => array('name' => 'testmoney2', 'monetary_type_id' => 2), - ), - ), - - 'Transaction' => array - ( - 'stamp' => '06/18/2009', - 'comment' => 'no comment', - ), - ); - - $data = -/* array( */ -/* 'LedgerEntry' => array */ -/* ( */ -/* '0' => */ - array( - 'amount' => 100, - 'debit_ledger_id' => 1, - 'credit_ledger_id' => 2, - 'transaction_id' => 66, - 'DebitReconciliationLedgerEntry' => - array('amount' => 44, - //'debit_ledger_entry_id' - 'credit_ledger_entry_id' => 17, - ), -/* ), */ -/* ), */ - - ); - - //$M = new Transaction(); - $M = new LedgerEntry(); - $M->create(); - $retval = $M->saveAll($data, - array( - 'validate' => false, - 'fieldList' => array(), - 'callbacks' => true, - )); - - $mid = $M->id; - pr(compact('retval', 'mid')); - if ($mid) { - $this->Session->setFlash(__("New Transaction Created ($mid)!", true)); - //$this->redirect(array('action'=>'view', $mid)); - } - else { - $this->autoRender = false; - pr(array('checkpoint' => "saveAll failed")); - } - - -/* $LE = new LedgerEntry(); */ -/* $LE->create(); */ -/* $ret = $LE->save($data, */ -/* array( */ -/* 'validate' => false, */ -/* 'fieldList' => array(), */ -/* 'callbacks' => true, */ -/* )); */ -/* $leid = $LE->id; */ -/* pr(array('checkpoint' => "New Ledger Entry", */ -/* compact('leid', 'ret'))); */ - //pr($LE); - - } } diff --git a/models/account.php b/models/account.php index 05c5a39..792eda7 100644 --- a/models/account.php +++ b/models/account.php @@ -92,6 +92,23 @@ class Account extends AppModel { function invoiceAccountID() { return $this->nameToID('Invoice'); } function receiptAccountID() { return $this->nameToID('Receipt'); } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: currentLedgerID + * - Returns the current ledger ID of the account + */ + function currentLedgerID($id) { + $this->cacheQueries = true; + $item = $this->find('first', array + ('contain' => 'CurrentLedger', + 'conditions' => array('Account.id' => $id), + )); + $this->cacheQueries = false; + return $item['CurrentLedger']['id']; + } + + /************************************************************************** ************************************************************************** @@ -131,6 +148,34 @@ class Account extends AppModel { } + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: closeCurrentLedger + * - Closes the current account ledger, and opens a new one + * with the old balance carried forward. + */ + function closeCurrentLedger($id = null) { + $contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id'))); + + $this->cacheQueries = true; + $account = $this->find('all', array + ('contain' => $contain, + 'fields' => array(), + 'conditions' => + $id ? array(array('Account.id' => $id)) : array() + )); + $this->cacheQueries = false; + //pr(compact('id', 'account')); + + foreach ($account AS $acct) { + if (!$this->Ledger->closeLedger($acct['CurrentLedger']['id'])) + return false; + } + return true; + } + + /************************************************************************** ************************************************************************** ************************************************************************** @@ -222,7 +267,7 @@ class Account extends AppModel { ('fields' => array(), "LedgerEntry" => array ('class' => "{$ucfund}LedgerEntry", - 'fields' => array('id', 'amount'), + 'fields' => array('id', 'customer_id', 'lease_id', 'amount'), "ReconciliationLedgerEntry" => array ('class' => "{$ucfund}ReconciliationLedgerEntry", 'fields' => array diff --git a/models/ledger.php b/models/ledger.php index aa10e76..7e159aa 100644 --- a/models/ledger.php +++ b/models/ledger.php @@ -44,6 +44,72 @@ class Ledger extends AppModel { ); + /************************************************************************** + ************************************************************************** + ************************************************************************** + * function: closeLedger + * - Closes the current ledger, and returns a fresh one + */ + function closeLedger($id) { + $this->recursive = -1; + + $stamp = date('Y-m-d G:i:s'); + $this->id = $id; + $this->read(); + $this->data['Ledger']['close_stamp'] = $stamp; + $this->data['Ledger']['closed'] = 1; + $this->save($this->data, false); + + $stats = $this->stats($id); + + $this->read(); + $this->data['Ledger']['id'] = null; + $this->data['Ledger']['closed'] = 0; + $this->data['Ledger']['open_stamp'] = $stamp; + $this->data['Ledger']['close_stamp'] = null; + $this->data['Ledger']['comment'] = null; + ++$this->data['Ledger']['sequence']; + $this->id = null; + $this->save($this->data, false); + //pr($this->data); + + if ($stats['balance'] == 0) + return $this->id; + + $this->read(); + $ftype = $this->Account->fundamentalType($this->data['Ledger']['account_id']); + $otype = $this->Account->fundamentalOpposite($ftype); + + // Create a transaction for balance transfer + $transaction = new Transaction(); + $transaction->create(); + if (!$transaction->save(array(), + array('validate' => false, + ))) { + return null; + } + + // Create an entry to carry the balance forward + $carry_entry_data = array + ($ftype.'_ledger_id' => $this->id, + $otype.'_ledger_id' => $id, + 'transaction_id' => $transaction->id, + 'amount' => $stats['balance'], + 'comment' => "Ledger Balance Forward", + ); + + $carry_entry = new LedgerEntry(); + $carry_entry->create(); + if (!$carry_entry->save($carry_entry_data, + array('validate' => false, + ))) { + return null; + } + + return $this->id; + } + + /************************************************************************** ************************************************************************** ************************************************************************** diff --git a/models/ledger_entry.php b/models/ledger_entry.php index b528820..b51b203 100644 --- a/models/ledger_entry.php +++ b/models/ledger_entry.php @@ -8,6 +8,16 @@ class LedgerEntry extends AppModel { 'amount' => array('money') ); + var $hasMany = array( + 'DebitReconciliation' => array( + 'className' => 'Reconciliation', + 'foreignKey' => 'debit_ledger_entry_id', + ), + 'CreditReconciliation' => array( + 'className' => 'Reconciliation', + 'foreignKey' => 'credit_ledger_entry_id', + ), + ); var $belongsTo = array( 'MonetarySource', 'Transaction', diff --git a/models/reconciliation.php b/models/reconciliation.php new file mode 100644 index 0000000..214b3d0 --- /dev/null +++ b/models/reconciliation.php @@ -0,0 +1,15 @@ + array( + 'className' => 'LedgerEntry', + //'foreignKey' => 'credit_ledger_entry_id', + ), + 'CreditLedgerEntry' => array( + 'className' => 'LedgerEntry', + //'foreignKey' => 'credit_ledger_entry_id', + ), + ); + +} diff --git a/models/transaction.php b/models/transaction.php index 2c22348..e7ccb03 100644 --- a/models/transaction.php +++ b/models/transaction.php @@ -7,7 +7,6 @@ class Transaction extends AppModel { /* ); */ var $belongsTo = array( - 'Customer', ); var $hasMany = array( @@ -17,10 +16,15 @@ class Transaction extends AppModel { function beforeSave() { - if(!empty($this->data['Transaction']['stamp'])) { + if(isset($this->data['Transaction']['stamp']) && + $this->data['Transaction']['stamp'] !== 'CURRENT_TIMESTAMP') { $this->data['Transaction']['stamp'] = $this->dateFormatBeforeSave($this->data['Transaction']['stamp']); } + else { + $this->data['Transaction']['stamp'] = null; + } + return true; } diff --git a/views/accounts/view.ctp b/views/accounts/view.ctp index d31b926..cf21063 100644 --- a/views/accounts/view.ctp +++ b/views/accounts/view.ctp @@ -65,7 +65,7 @@ echo $this->element('ledgers', */ echo $this->element('ledger_entries', - array('caption' => "Current Ledger: (#{$account['CurrentLedger']['sequence']})", + array('caption' => "Current Ledger: (#{$account['Account']['id']}-{$account['CurrentLedger']['sequence']})", 'ledger_id' => $account['CurrentLedger']['id'], 'account_type' => $account['Account']['type'], )); diff --git a/views/customers/payment.ctp b/views/customers/payment.ctp index 4c3dac4..1ac95c0 100644 --- a/views/customers/payment.ctp +++ b/views/customers/payment.ctp @@ -148,6 +148,17 @@ $grid_setup['onSelectRow'] = array $('#payments').html(''); $('#payment-id').val(0); addPaymentSource(false); + datepickerNow(); + } + + function datepickerNow() { + now = new Date(); + $("#datepicker").val($.datepicker.formatDate('mm/dd/yy', now) + + ' ' + + (now.getHours() < 10 ? '0' : '') + + now.getHours() + ':' + + (now.getMinutes() < 10 ? '0' : '') + + now.getMinutes()); } function addPaymentSource(flash) { @@ -158,16 +169,6 @@ $grid_setup['onSelectRow'] = array '
' + 20090616: - * MUST GET THIS FROM THE DATABASE!! - * HARDCODED VALUES BAD... VERY BAD... - */ - $monetary_type_ids = array('Cash' => 2, - 'Check' => 3, - 'Money Order' => 4, - 'ACH' => 5, - 'Credit Card' => 7, - ); $types = array(); foreach(array('Cash', 'Check', 'Money Order', /*'ACH', 'Credit Card'*/) AS $name) @@ -175,11 +176,10 @@ $grid_setup['onSelectRow'] = array foreach ($types AS $type => $name) { $div = '
'; - $div .= ''; - $div .= ''; + $div .= ' VALUE="'.$name.'" ' . ($name == 'Cash' ? 'CHECKED ' : '') . '/>'; $div .= ' '; $div .= '
'; echo "'$div' +\n"; @@ -228,7 +228,7 @@ function switchPaymentType(paymentid, type) { '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
'; break; @@ -238,7 +238,7 @@ function switchPaymentType(paymentid, type) { '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
'; break; @@ -248,14 +248,14 @@ function switchPaymentType(paymentid, type) { '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
' + '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
'; break; @@ -265,21 +265,21 @@ function switchPaymentType(paymentid, type) { '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
' + '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
' + '
' + ' ' + // REVISIT : 20090617: Use comment field for now. - ' ' + '
'; break; @@ -436,7 +436,8 @@ echo $form->create(null, array('id' => 'payment-form',
' . "\n"; +echo 'Date: '; +echo ' Now
' . "\n"; echo 'Comment:
' . "\n"; echo $form->end('Post Payment'); @@ -457,8 +458,12 @@ echo $form->end('Post Payment');