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) {
'
';
break;
@@ -238,7 +238,7 @@ function switchPaymentType(paymentid, type) {
'
';
break;
@@ -248,14 +248,14 @@ function switchPaymentType(paymentid, type) {
'
' +
'
';
break;
@@ -265,21 +265,21 @@ function switchPaymentType(paymentid, type) {
'
' +
'
' +
'
';
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');