Further tweaked the code to add new transactions, primarily by creating separate verification and addition functions in each underlying model. There are logic changes as well, such as adding in the other half of the missing double entry (as well as the double entry itself). This checkin also introduces the creation of CREDIT statement entries, when the customer has overpayed. It's working fairly well, although undoubtedly will need more tweaking.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716/site@394 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
@@ -372,6 +372,7 @@ class Customer extends AppModel {
|
||||
'fields' => $this->StatementEntry->chargePaymentFields(true),
|
||||
'conditions' => array('StatementEntry.customer_id' => $id),
|
||||
));
|
||||
$find_stats = $find_stats[0];
|
||||
pr(compact('find_stats'));
|
||||
|
||||
$tquery = $query;
|
||||
@@ -395,7 +396,8 @@ class Customer extends AppModel {
|
||||
$ar_transaction_stats += $ar_transaction_stats['LedgerEntry'];
|
||||
pr(compact('ar_transaction_stats'));
|
||||
|
||||
$stats = $ar_transaction_stats;
|
||||
//$stats = $ar_transaction_stats;
|
||||
$stats = $find_stats;
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,4 +10,86 @@ class DoubleEntry extends AppModel {
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyDoubleEntry
|
||||
* - Verifies consistenty of new double entry data
|
||||
* (not in a pre-existing double entry)
|
||||
*/
|
||||
function verifyDoubleEntry($entry1, $entry2, $entry1_tender = null) {
|
||||
pr(array("DoubleEntry::verifyDoubleEntry()"
|
||||
=> compact('entry1', 'entry2', 'entry1_tender')));
|
||||
|
||||
$LE = new LedgerEntry();
|
||||
if (!$LE->verifyLedgerEntry($entry1, $entry1_tender)) {
|
||||
pr(array("DoubleEntry::verifyDoubleEntry()"
|
||||
=> "Entry1 verification failed"));
|
||||
return false;
|
||||
}
|
||||
if (!$LE->verifyLedgerEntry($entry2)) {
|
||||
pr(array("DoubleEntry::verifyDoubleEntry()"
|
||||
=> "Entry2 verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(($entry1['crdr'] === 'DEBIT' && $entry2['crdr'] === 'CREDIT') ||
|
||||
($entry1['crdr'] === 'CREDIT' && $entry2['crdr'] === 'DEBIT'))) {
|
||||
pr(array("DoubleEntry::verifyDoubleEntry()"
|
||||
=> "Double Entry verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addDoubleEntry
|
||||
* - Inserts new Double Entry into the database
|
||||
*/
|
||||
|
||||
function addDoubleEntry($entry1, $entry2, $entry1_tender = null) {
|
||||
pr(array('DoubleEntry::addDoubleEntry' =>
|
||||
compact('entry1', 'entry2', 'entry1_tender')));
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyDoubleEntry($entry1, $entry2, $entry1_tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Since this model only relates to DebitEntry and CreditEntry...
|
||||
$LE = new LedgerEntry();
|
||||
|
||||
// Add the first ledger entry to the database
|
||||
$result = $LE->addLedgerEntry($entry1, $entry1_tender);
|
||||
$ret['Entry1'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Add the second ledger entry to the database
|
||||
$result = $LE->addLedgerEntry($entry2);
|
||||
$ret['Entry2'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Now link them as a double entry
|
||||
$double_entry = array();
|
||||
$double_entry['debit_entry_id'] =
|
||||
($entry1['crdr'] === 'DEBIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id'];
|
||||
$double_entry['credit_entry_id'] =
|
||||
($entry1['crdr'] === 'CREDIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id'];
|
||||
|
||||
pr(array('DoubleEntry::addDoubleEntry' =>
|
||||
array('checkpoint' => 'Pre-Save')
|
||||
+ compact('double_entry')));
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($double_entry))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$ret['double_entry_id'] = $this->id;
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ class LedgerEntry extends AppModel {
|
||||
|
||||
var $hasOne = array(
|
||||
'Tender',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
@@ -78,189 +79,74 @@ class LedgerEntry extends AppModel {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addCharge
|
||||
* - Adds a new charge
|
||||
* function: verifyLedgerEntry
|
||||
* - Verifies consistenty of new ledger entry data
|
||||
* (not in a pre-existing ledger entry)
|
||||
*/
|
||||
function verifyLedgerEntry($entry, $tender = null) {
|
||||
pr(array("LedgerEntry::verifyLedgerEntry()"
|
||||
=> compact('entry', 'tender')));
|
||||
|
||||
function addCharge($data, $transaction, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
if (empty($entry['account_id']) ||
|
||||
empty($entry['crdr']) ||
|
||||
empty($entry['amount'])
|
||||
) {
|
||||
pr(array("LedgerEntry::verifyLedgerEntry()"
|
||||
=> "Entry verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key charge parameters
|
||||
$charge = array_intersect_key($data, array('stamp'=>1, 'amount'=>1, 'account_id'=>1));
|
||||
|
||||
$charge['customer_id'] = $customer_id;
|
||||
$charge['lease_id'] = $lease_id;
|
||||
$charge['type'] = 'CHARGE';
|
||||
|
||||
$ids = $this->Entry->Ledger->Account->postLedgerEntry
|
||||
($transaction,
|
||||
$charge,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
|
||||
) + $data
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
return $ids['charge_id'];
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addPayment
|
||||
* - Adds a new payment
|
||||
*/
|
||||
|
||||
function addPayment($data, $transaction, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Establish the key payment parameters
|
||||
$payment = array_intersect_key($data, array('stamp'=>1, 'name'=>1, 'monetary_type'=>1,
|
||||
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1,
|
||||
'amount'=>1, 'account_id'=>1));
|
||||
$payment['customer_id'] = $customer_id;
|
||||
$payment['type'] = 'PAYMENT';
|
||||
|
||||
$ids = $this->Entry->Ledger->Account->postLedgerEntry
|
||||
($transaction,
|
||||
$payment,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
|
||||
) + $data
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
return $ids['payment_id'];
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reverse
|
||||
* - Reverses the charges
|
||||
*
|
||||
* SAMPLE MOVE IN w/ PRE PAYMENT
|
||||
* DEPOSIT RENT A/R RECEIPT CHECK PETTY BANK
|
||||
* ------- ------- ------- ------- ------- ------- -------
|
||||
* |25 | 25| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | | |25 25| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | | |85 85| | |
|
||||
* | | | | |85 | 85|
|
||||
|
||||
* MOVE OUT and REFUND FINAL MONTH
|
||||
* DEPOSIT RENT C/P RECEIPT CHECK PETTY BANK
|
||||
* ------- ------- ------- ------- ------- ------- -------
|
||||
* 25| | |25 | | | | t20 e20a
|
||||
* | 20| |20 | | | | t20 e20b
|
||||
|
||||
* -ONE REFUND CHECK-
|
||||
* | | 25| |25 | | | t30 e30a
|
||||
* | | 20| |20 | | | t30 e30b
|
||||
* | | | 45| | | |45 t40 e40
|
||||
* -OR MULTIPLE-
|
||||
* | | 15| |15 | | | t50a e50a
|
||||
* | | | 15| | |15 | t60a e60a
|
||||
* | | 30| |30 | | | t50b e50b
|
||||
* | | | 30| | | |30 t60b e60b
|
||||
* | | | | | | |
|
||||
|
||||
|
||||
OPTION 1
|
||||
* |-25 | -25| | | | |
|
||||
* | |-20 -20| | | | |
|
||||
* | | |-25 -25| | | |
|
||||
* | | |-20 -20| | | |
|
||||
|
||||
OPTION 2
|
||||
* |-25 | | -25| | | |
|
||||
* | |-20 | -20| | | |
|
||||
* | | | |-15 | -15| |
|
||||
* | | | |-30 | | -30|
|
||||
* | | | | | | |
|
||||
*
|
||||
*/
|
||||
function reverse($ledger_entries, $stamp = null) {
|
||||
pr(array('Entry::reverse',
|
||||
compact('ledger_entries', 'stamp')));
|
||||
|
||||
// If the user only wants to reverse one ID, we'll allow it
|
||||
if (!is_array($ledger_entries))
|
||||
$ledger_entries = $this->find
|
||||
('all', array
|
||||
('contain' => false,
|
||||
'conditions' => array('Entry.id' => $ledger_entries)));
|
||||
|
||||
$A = new Account();
|
||||
|
||||
$ar_account_id = $A->accountReceivableAccountID();
|
||||
$receipt_account_id = $A->receiptAccountID();
|
||||
|
||||
$transaction_id = null;
|
||||
foreach ($ledger_entries AS $entry) {
|
||||
$entry = $entry['Entry'];
|
||||
$amount = -1*$entry['amount'];
|
||||
|
||||
if (isset($entry['credit_account_id']))
|
||||
$refund_account_id = $entry['credit_account_id'];
|
||||
elseif (isset($entry['CreditLedger']['Account']['id']))
|
||||
$refund_account_id = $entry['CreditLedger']['Account']['id'];
|
||||
elseif (isset($entry['credit_ledger_id']))
|
||||
$refund_account_id = $this->Ledger->accountID($entry['credit_ledger_id']);
|
||||
else
|
||||
return null;
|
||||
|
||||
// post new refund in the income account
|
||||
$ids = $A->postEntry
|
||||
(array('transaction_id' => $transaction_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),
|
||||
'credit_ledger_id' => $A->currentLedgerID($refund_account_id),
|
||||
'effective_date' => $entry['effective_date'],
|
||||
'through_date' => $entry['through_date'],
|
||||
'amount' => $amount,
|
||||
'lease_id' => $entry['lease_id'],
|
||||
'customer_id' => $entry['customer_id'],
|
||||
'comment' => "Refund; Entry #{$entry['id']}",
|
||||
),
|
||||
array('debit' => array
|
||||
(array('Entry' =>
|
||||
array('id' => $entry['id'],
|
||||
'amount' => $amount))),
|
||||
)
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
return null;
|
||||
$transaction_id = $ids['transaction_id'];
|
||||
|
||||
pr(array('checkpoint' => 'Posted Refund Ledger Entry',
|
||||
compact('ids', 'amount', 'refund_account_id', 'ar_account_id')));
|
||||
if (isset($tender) && !$this->Tender->verifyTender($tender)) {
|
||||
pr(array("LedgerEntry::verifyLedgerEntry()"
|
||||
=> "Tender verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addLedgerEntry
|
||||
* - Inserts new Ledger Entry into the database
|
||||
*/
|
||||
function addLedgerEntry($entry, $tender = null) {
|
||||
pr(array('LedgerEntry::addLedgerEntry' =>
|
||||
compact('entry', 'tender')));
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyLedgerEntry($entry, $tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
if (empty($entry['ledger_id']))
|
||||
$entry['ledger_id'] =
|
||||
$this->Account->currentLedgerID($entry['account_id']);
|
||||
|
||||
pr(array('LedgerEntry::addLedgerEntry' =>
|
||||
array('checkpoint' => 'Pre-Save')
|
||||
+ compact('entry')));
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($entry))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$ret['ledger_entry_id'] = $this->id;
|
||||
|
||||
if (isset($tender)) {
|
||||
$tender['account_id'] = $entry['account_id'];
|
||||
$tender['ledger_entry_id'] = $ret['ledger_entry_id'];
|
||||
$result = $this->Tender->addTender($tender);
|
||||
$ret['Tender'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
}
|
||||
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
|
||||
@@ -56,6 +56,58 @@ class StatementEntry extends AppModel {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyStatementEntry
|
||||
* - Verifies consistenty of new statement entry data
|
||||
* (not in a pre-existing statement entry)
|
||||
*/
|
||||
function verifyStatementEntry($entry) {
|
||||
pr(array("StatementEntry::verifyStatementEntry()"
|
||||
=> compact('entry')));
|
||||
|
||||
if (empty($entry['type']) ||
|
||||
//empty($entry['effective_date']) ||
|
||||
empty($entry['account_id']) ||
|
||||
empty($entry['amount'])
|
||||
) {
|
||||
pr(array("StatementEntry::verifyStatementEntry()"
|
||||
=> "Entry verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addStatementEntry
|
||||
* - Inserts new Statement Entry into the database
|
||||
*/
|
||||
function addStatementEntry($entry) {
|
||||
pr(array('StatementEntry::addStatementEntry' =>
|
||||
compact('entry')));
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyStatementEntry($entry))
|
||||
return array('error' => true, 'verify_data' => $entry) + $ret;
|
||||
|
||||
pr(array('StatementEntry::addStatementEntry' =>
|
||||
array('checkpoint' => 'Pre-Save')
|
||||
+ compact('entry')));
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($entry))
|
||||
return array('error' => true, 'save_data' => $entry) + $ret;
|
||||
|
||||
$ret['statement_entry_id'] = $this->id;
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -354,12 +406,15 @@ OPTION 2
|
||||
* - Assigns all credits to existing charges
|
||||
*/
|
||||
function assignCredits($query = null, $receipt_id = null) {
|
||||
pr(array('StatementEntry::assignCredits' => compact('query')));
|
||||
pr(array('StatementEntry::assignCredits' => compact('query', 'receipt_id')));
|
||||
$this->queryInit($query);
|
||||
|
||||
$ret = array();
|
||||
|
||||
// First, find all known credits
|
||||
$lquery = $query;
|
||||
$lquery['conditions'][] = array('StatementEntry.type' => 'CREDIT');
|
||||
$lquery['order'][] = 'StatementEntry.effective_date ASC';
|
||||
$credits = $this->find('all', $lquery);
|
||||
pr(compact('lquery', 'credits'));
|
||||
|
||||
@@ -377,8 +432,7 @@ OPTION 2
|
||||
$lquery['link'] = array('StatementEntry' => array('fields' => array()) + $lquery['link']);
|
||||
$lquery['conditions'][] = array('Transaction.type' => 'RECEIPT');
|
||||
$lquery['fields'] = array('Transaction.id', 'Transaction.stamp', 'Transaction.amount',
|
||||
//'SUM(StatementEntry.amount) AS applied_amount',
|
||||
'Transaction.amount - SUM(StatementEntry.amount) AS balance');
|
||||
'Transaction.amount - SUM(COALESCE(StatementEntry.amount,0)) AS balance');
|
||||
$lquery['group'] = 'Transaction.id HAVING balance > 0';
|
||||
$anon_credits = $this->Transaction->find('all', $lquery);
|
||||
foreach ($anon_credits AS &$ac) {
|
||||
@@ -387,6 +441,20 @@ OPTION 2
|
||||
}
|
||||
pr(compact('lquery', 'anon_credits'));
|
||||
|
||||
// Next, add in the credits from the newly added receipt
|
||||
if (!empty($receipt_id)) {
|
||||
$lquery = $query;
|
||||
$lquery['conditions'][] = array('Transaction.type' => 'RECEIPT');
|
||||
$lquery['conditions'][] = array('Transaction.id' => $receipt_id);
|
||||
$lquery['fields'] = array('Transaction.id', 'Transaction.stamp', 'Transaction.amount',
|
||||
'Transaction.amount AS balance');
|
||||
$receipt_credit = $this->Transaction->find('first', $lquery);
|
||||
if ($receipt_credit)
|
||||
$anon_credits[] = $receipt_credit;
|
||||
|
||||
pr(compact('lquery', 'anon_credits'));
|
||||
}
|
||||
|
||||
// REVISIT <AP>: 20090726
|
||||
// This algorithm shouldn't be hardcoded. We need to allow
|
||||
// the user to specify how payments should be applied.
|
||||
@@ -495,10 +563,10 @@ OPTION 2
|
||||
array('checkpoint' => 'New Payment Entry')
|
||||
+ compact('payment')));
|
||||
|
||||
$SE = new StatementEntry();
|
||||
$SE->create();
|
||||
if (!$SE->save($payment))
|
||||
die("UNABLE TO SAVE NEW PAYMENT ENTRY");
|
||||
$result = $this->addStatementEntry($payment);
|
||||
$ret['Payment'][] = $result;
|
||||
if ($result['error'])
|
||||
$ret['error'] = true;
|
||||
|
||||
// Adjust the charge balance to reflect the new payment
|
||||
$charge['balance'] -= $payment_amount;
|
||||
@@ -512,17 +580,15 @@ OPTION 2
|
||||
|
||||
}
|
||||
|
||||
// Make partially used credits are added to the used list
|
||||
// Partially used credits must be added to the used list
|
||||
if (isset($credits[0]['applied']))
|
||||
$used_credits[] = array_shift($credits);
|
||||
if (isset($anon_credits[0]['applied']))
|
||||
$used_anon_credits[] = array_shift($anon_credits);
|
||||
|
||||
pr(array('StatementEntry::assignCredits' =>
|
||||
array('checkpoint' => 'Payments added')
|
||||
+ compact('credits', 'used_credits', 'anon_credits', 'used_anon_credits')));
|
||||
|
||||
// Finally, clean up any credits that have been used
|
||||
// Clean up any explicit credits that have been used
|
||||
foreach ($used_credits AS $credit) {
|
||||
if ($credit['balance'] > 0) {
|
||||
pr(array('StatementEntry::assignCredits' =>
|
||||
@@ -541,6 +607,29 @@ OPTION 2
|
||||
}
|
||||
}
|
||||
|
||||
// Convert non-exhausted implicit credits to explicit ones
|
||||
foreach ($anon_credits AS $credit) {
|
||||
if ($credit['balance'] <= 0)
|
||||
die("HOW DID EXHAUSTED ANON CREDITS GET LEFT?");
|
||||
|
||||
pr(array('StatementEntry::assignCredits' =>
|
||||
array('checkpoint' => 'Create Explicit Credit')
|
||||
+ compact('credit')));
|
||||
|
||||
$result = $this->addStatementEntry
|
||||
(array('type' => 'CREDIT',
|
||||
'account_id' => $this->Account->accountReceivableAccountID(),
|
||||
'amount' => $credit['balance'],
|
||||
'effective_date' => $credit['Transaction']['stamp'],
|
||||
'transaction_id' => $credit['Transaction']['id'],
|
||||
'customer_id' => $credit['Customer']['id'],
|
||||
));
|
||||
$ret['Credit'][] = $result;
|
||||
if ($result['error'])
|
||||
$ret['error'] = true;
|
||||
}
|
||||
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
@@ -565,16 +654,21 @@ OPTION 2
|
||||
$rquery['link']['PaymentEntry'] = array('fields' => array());
|
||||
|
||||
$rquery['fields'] = array();
|
||||
$rquery['fields'][] = "SUM(StatementEntry.amount) AS total";
|
||||
$rquery['fields'][] = "StatementEntry.amount";
|
||||
$rquery['fields'][] = "SUM(PaymentEntry.amount) AS reconciled";
|
||||
$rquery['fields'][] = "SUM(StatementEntry.amount - COALESCE(PaymentEntry.amount,0)) AS balance";
|
||||
|
||||
$rquery['conditions'][] = array('StatementEntry.type' => 'CHARGE');
|
||||
$result = $this->find('first', $rquery);
|
||||
if (!isset($result[0]['balance']))
|
||||
$result[0]['balance'] = 0;
|
||||
$stats['Charge'] = $result[0];
|
||||
$rquery['group'] = 'StatementEntry.id';
|
||||
|
||||
$result = $this->find('all', $rquery);
|
||||
$stats['Charge'] = array('total' => 0, 'reconciled' => 0);
|
||||
foreach($result AS $charge) {
|
||||
$stats['Charge']['total'] += $charge['StatementEntry']['amount'];
|
||||
$stats['Charge']['reconciled'] += $charge[0]['reconciled'];
|
||||
}
|
||||
$stats['Charge']['balance'] =
|
||||
$stats['Charge']['total'] - $stats['Charge']['reconciled'];
|
||||
|
||||
/* pr(array('StatementEntry::stats' => */
|
||||
/* array('checkpoint' => 'Charges') */
|
||||
/* + compact('query', 'result'))); */
|
||||
|
||||
@@ -13,43 +13,68 @@ class Tender extends AppModel {
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyTender
|
||||
* - Verifies consistenty of new tender data
|
||||
* (not in a pre-existing tender)
|
||||
*/
|
||||
function verifyTender($tender) {
|
||||
pr(array("Tender::verifyTender()"
|
||||
=> compact('tender')));
|
||||
|
||||
if (empty($tender['tender_type_id'])) {
|
||||
pr(array("Tender::verifyTender()"
|
||||
=> "Tender verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addTender
|
||||
* - Adds a new tender
|
||||
* - Inserts new Tender into the database
|
||||
*/
|
||||
|
||||
function addTender($data, $customer_id, $lease_id = null, $reconcile = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
function addTender($tender) {
|
||||
pr(array('Tender::addTender' =>
|
||||
compact('tender')));
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
$ret = array();
|
||||
if (!$this->verifyTender($tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Establish the key tender parameters
|
||||
$tender = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1,
|
||||
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1));
|
||||
$tender['customer_id'] = $customer_id;
|
||||
// Come up with a (not necessarily unique) name for the tender.
|
||||
// For checks & money orders, this will be based on the check
|
||||
// number. For other types of tender, we'll just use the
|
||||
// generic name of the monetary account.
|
||||
// REVISIT <AP>: 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($tender['name']) && !empty($tender['account_id'])) {
|
||||
$tender['name'] = $this->LedgerEntry->Account->name($tender['account_id']);
|
||||
if ($tender['account_id'] == $this->LedgerEntry->Account->checkAccountID() ||
|
||||
$tender['account_id'] == $this->LedgerEntry->Account->moneyOrderAccountID()) {
|
||||
$tender['name'] .= ' #' . $tender['data1'];
|
||||
}
|
||||
}
|
||||
|
||||
// Create the tender entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a tender.
|
||||
$ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry
|
||||
($tender,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->tenderAccountID())
|
||||
) + $entry,
|
||||
array('debit' => 'tender',
|
||||
'credit' => $reconcile)
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
pr(array('Tender::addTender' =>
|
||||
array('checkpoint' => 'Pre-Save')
|
||||
+ array('Tender' => $tender)));
|
||||
|
||||
$tender = array_intersect_key($ids,
|
||||
array('tender_id'=>1,
|
||||
'split_tender_id'=>1));
|
||||
return $ret;
|
||||
$this->create();
|
||||
if (!$this->save($tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$ret['tender_id'] = $this->id;
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -93,6 +93,50 @@ class Transaction extends AppModel {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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 (!$this->StatementEntry->verifyStatementEntry($se)) {
|
||||
pr(array("Transaction::verifyTransaction()"
|
||||
=> "Statement Entry verification failed"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -152,7 +196,11 @@ class Transaction extends AppModel {
|
||||
*/
|
||||
|
||||
function addTransaction($data, $customer_id, $lease_id = null) {
|
||||
pr(compact('data', 'customer_id', 'lease_id'));
|
||||
pr(array("Transaction::addTransaction()"
|
||||
=> compact('data', 'customer_id', 'lease_id')));
|
||||
|
||||
if (!isset($data['Transaction']) || !isset($data['Entry']))
|
||||
return array('error' => true);
|
||||
|
||||
// Automatically figure out the customer if we have the lease
|
||||
if (!empty($lease_id) && empty($customer_id)) {
|
||||
@@ -162,86 +210,74 @@ class Transaction extends AppModel {
|
||||
$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 = $data['Transaction'];
|
||||
$transaction['customer_id'] = $customer_id;
|
||||
$transaction['lease_id'] = $lease_id;
|
||||
$transaction['ledger_id'] =
|
||||
$this->Account->currentLedgerID($transaction['account_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 (!empty($entry['comment']) && $entry['type'] === 'PAYMENT')
|
||||
$entry['ledger_entry_comment'] = $entry['comment'];
|
||||
else
|
||||
$entry['ledger_entry_comment'] = null;
|
||||
}
|
||||
if (empty($entry['statement_entry_comment'])) {
|
||||
if (!empty($entry['comment']) && $entry['type'] === 'CHARGE')
|
||||
$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')));
|
||||
|
||||
// Create a statement entry
|
||||
$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'], $customer_id, $lease_id))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
pr(array('Transaction::addTransaction' =>
|
||||
array('checkpoint' => 'Pre Transaction Save')
|
||||
+ compact('transaction')));
|
||||
@@ -249,104 +285,39 @@ class Transaction extends AppModel {
|
||||
// Save transaction to the database
|
||||
$this->create();
|
||||
if (!$this->save($transaction))
|
||||
return array('error' => true);
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Set up our return ids array
|
||||
$ret = array('transaction_id' => $this->id,
|
||||
'Entry' => array(),
|
||||
'error' => false);
|
||||
$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'] = $ret['transaction_id'];
|
||||
$le2['transaction_id'] = $ret['transaction_id'];
|
||||
$se['transaction_id'] = $ret['transaction_id'];
|
||||
|
||||
$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;
|
||||
$result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender);
|
||||
$ret['entries'][$e_index]['DoubleEntry'] = $result;
|
||||
if ($result['error']) {
|
||||
$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 <AP>: 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') {
|
||||
if ($se['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;
|
||||
$result = $this->StatementEntry->addStatementEntry($se);
|
||||
$ret['entries'][$e_index]['StatementEntry'] = $result;
|
||||
if ($result['error']) {
|
||||
$ret['error'] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// REVISIT <AP>: 20090723
|
||||
// Now that we have new entries, WE MUST RECONCILE
|
||||
// THE CHARGES TO CUSTOMER ACCOUNT BALANCE! This
|
||||
@@ -354,6 +325,19 @@ class Transaction extends AppModel {
|
||||
// the only way to make use of a positive customer
|
||||
// balance if new charges have been entered.
|
||||
|
||||
if (!$ret['error']) {
|
||||
$result = $this->StatementEntry->assignCredits
|
||||
(array('link' => array('Customer'),
|
||||
'conditions' => array('Customer.id' => $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)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user