git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716/site@562 97e9348a-65ac-dc4b-aefc-98561f571b83
1055 lines
36 KiB
PHP
1055 lines
36 KiB
PHP
<?php
|
|
class Transaction extends AppModel {
|
|
|
|
var $belongsTo = array(
|
|
'Customer',
|
|
'Account',
|
|
'Ledger',
|
|
);
|
|
|
|
var $hasMany = array(
|
|
'LedgerEntry' => array(
|
|
'dependent' => true,
|
|
),
|
|
'StatementEntry' => array(
|
|
'dependent' => true,
|
|
),
|
|
|
|
'DepositTender' => array(
|
|
'className' => 'Tender',
|
|
'foreignKey' => 'deposit_transaction_id',
|
|
),
|
|
|
|
'Charge' => array(
|
|
'className' => 'StatementEntry',
|
|
'conditions' => array('Charge.type' => 'CHARGE')
|
|
),
|
|
|
|
'Disbursement' => array(
|
|
'className' => 'StatementEntry',
|
|
'conditions' => array('Disbursement.type' => 'DISBURSEMENT')
|
|
),
|
|
|
|
'Debit' => array(
|
|
'className' => 'LedgerEntry',
|
|
'conditions' => array('Debit.crdr' => 'DEBIT')
|
|
),
|
|
|
|
'Credit' => array(
|
|
'className' => 'LedgerEntry',
|
|
'conditions' => array('Credit.crdr' => 'CREDIT')
|
|
),
|
|
|
|
);
|
|
|
|
|
|
var $default_log_level = array('log' => 30, 'show' => 15);
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addInvoice
|
|
* - Adds a new invoice invoice
|
|
*/
|
|
|
|
function addInvoice($data, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => true,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => true,
|
|
);
|
|
|
|
// Establish the transaction as an invoice
|
|
$data['Transaction'] +=
|
|
array('type' => 'INVOICE',
|
|
'crdr' => 'DEBIT',
|
|
'account_id' => $this->Account->accountReceivableAccountID(),
|
|
'customer_id' => $customer_id,
|
|
'lease_id' => $lease_id,
|
|
);
|
|
|
|
// Go through the statement entries and flag as charges
|
|
foreach ($data['Entry'] AS &$entry)
|
|
$entry += array('type' => 'CHARGE',
|
|
'crdr' => 'CREDIT',
|
|
);
|
|
|
|
$ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['invoice_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addReceipt
|
|
* - Adds a new receipt
|
|
*/
|
|
|
|
function addReceipt($data, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => true,
|
|
'assign_receipt' => true,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => false,
|
|
);
|
|
|
|
// Establish the transaction as a receipt
|
|
$data['Transaction'] +=
|
|
array('type' => 'RECEIPT',
|
|
'crdr' => 'CREDIT',
|
|
'account_id' => $this->Account->accountReceivableAccountID(),
|
|
'customer_id' => $customer_id,
|
|
'lease_id' => $lease_id,
|
|
);
|
|
|
|
// Go through the statement entries and flag as disbursements
|
|
foreach ($data['Entry'] AS &$entry)
|
|
$entry += array('type' => 'DISBURSEMENT', // not used
|
|
'crdr' => 'DEBIT',
|
|
'account_id' =>
|
|
(isset($entry['Tender']['tender_type_id'])
|
|
? ($this->LedgerEntry->Tender->TenderType->
|
|
accountID($entry['Tender']['tender_type_id']))
|
|
: null),
|
|
);
|
|
|
|
$ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['receipt_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addWaiver
|
|
* - Adds a new waiver
|
|
*/
|
|
|
|
function addWaiver($data, $charge_id, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'charge_id', 'customer_id', 'lease_id'));
|
|
|
|
if (count($data['Entry']) != 1)
|
|
$this->INTERNAL_ERROR("Should be one Entry for addWaiver");
|
|
|
|
// No assignment of credits, as we'll manually assign
|
|
// using charge_entry_id as part of the entry (below).
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => false,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => true,
|
|
);
|
|
|
|
// Just make sure the disbursement(s) are marked as waivers
|
|
// and that they go to cover the specific charge.
|
|
$data['Entry'][0] +=
|
|
array('type' => 'WAIVER',
|
|
'account_id' => $this->Account->waiverAccountID(),
|
|
'charge_entry_id' => $charge_id);
|
|
|
|
// In all other respects this is just a receipt.
|
|
$ids = $this->addReceipt($data, $customer_id, $lease_id);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['waiver_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addReversal
|
|
* - Adds a new charge reversal
|
|
*/
|
|
|
|
function addReversal($data, $charge_id, $credit_amount, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'charge_id', 'credit_amount', 'customer_id', 'lease_id'));
|
|
|
|
if (count($data['Entry']) != 1)
|
|
$this->INTERNAL_ERROR("Should be one Entry for addReversal");
|
|
|
|
$data += array('control' => array());
|
|
|
|
// First, reverse the charge
|
|
$reversal = $data;
|
|
$reversal['control'] +=
|
|
array('assign' => false,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => true,
|
|
);
|
|
|
|
$reversal['Transaction']['type'] = 'CREDIT_NOTE';
|
|
//$reversal['Transaction']['crdr'] = 'CREDIT';
|
|
$reversal['Entry'][0]['charge_entry_id'] = $charge_id;
|
|
$reversal['Entry'][0]['type'] = 'REVERSAL';
|
|
//$reversal['Entry'][0]['crdr'] = 'DEBIT';
|
|
$ids['reversal'] = $this->addReceipt($reversal, $customer_id, $lease_id);
|
|
|
|
// Then issue a credit for the amount already paid, if any
|
|
if ($credit_amount > 0) {
|
|
$ids['credit'] = $this->addReceipt
|
|
(array('control' =>
|
|
array('assign' => true,
|
|
'assign_receipt' => true,
|
|
'include_ledger_entry' => false,
|
|
'include_statement_entry' => true,
|
|
),
|
|
|
|
'Transaction' =>
|
|
array('stamp' => $data['Transaction']['stamp'],
|
|
),
|
|
|
|
'Entry' =>
|
|
array(array('amount' => $credit_amount,
|
|
'charge_entry_id' => $charge_id,
|
|
'account_id' => $this->StatementEntry->Account->accountPayableAccountID(),
|
|
))),
|
|
$customer_id, $lease_id);
|
|
}
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addWriteOff
|
|
* - Adds a new write off of bad debt
|
|
*/
|
|
|
|
function addWriteOff($data, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
|
|
|
if (count($data['Entry']) != 1)
|
|
$this->INTERNAL_ERROR("Should be one Entry for addWriteOff");
|
|
|
|
// Just make sure the disbursement(s) are marked as write offs
|
|
// and that the write-off account is used for the charge.
|
|
$data['Entry'][0] +=
|
|
array('type' => 'WRITEOFF',
|
|
'account_id' => $this->Account->badDebtAccountID());
|
|
|
|
// In all other respects this is just a receipt.
|
|
$ids = $this->addReceipt($data, $customer_id, $lease_id);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['writeoff_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addDeposit
|
|
* - Adds a new bank deposit
|
|
*/
|
|
|
|
function addDeposit($data, $account_id) {
|
|
$this->prEnter(compact('data', 'account_id'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => false,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => false,
|
|
);
|
|
|
|
// Establish the transaction as a deposit
|
|
$data['Transaction'] +=
|
|
array('type' => 'DEPOSIT',
|
|
'crdr' => 'DEBIT',
|
|
'account_id' => $account_id,
|
|
'customer_id' => null,
|
|
'lease_id' => null,
|
|
);
|
|
|
|
// Save the list of IDs, so that we can mark their
|
|
// deposit transaction after it has been created.
|
|
$tender_ids = array_map(create_function('$item', 'return $item["tender_id"];'),
|
|
$data['Entry']);
|
|
|
|
// Go through the statement entries and re-group by account id
|
|
$group = array();
|
|
foreach ($data['Entry'] AS &$entry) {
|
|
if (!isset($group[$entry['account_id']]))
|
|
$group[$entry['account_id']] =
|
|
array('account_id' => $entry['account_id'],
|
|
'crdr' => strtoupper($this->Account->fundamentalOpposite($data['Transaction']['crdr'])),
|
|
'amount' => 0);
|
|
$group[$entry['account_id']]['amount'] += $entry['amount'];
|
|
}
|
|
$data['Entry'] = $group;
|
|
|
|
$ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['deposit_id'] = $ids['transaction_id'];
|
|
|
|
if (!empty($ids['deposit_id'])) {
|
|
$this->LedgerEntry->Tender->updateAll
|
|
(array('Tender.deposit_transaction_id' => $ids['deposit_id']),
|
|
array('Tender.id' => $tender_ids)
|
|
);
|
|
}
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addClose
|
|
* - Adds a new transaction for closing ledgers
|
|
*/
|
|
|
|
function addClose($data) {
|
|
$this->prEnter(compact('data'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => false,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => false,
|
|
);
|
|
|
|
// Establish the transaction as a close
|
|
$data['Transaction'] +=
|
|
array('type' => 'CLOSE',
|
|
'crdr' => null,
|
|
'account_id' => null,
|
|
'customer_id' => null,
|
|
'lease_id' => null,
|
|
);
|
|
|
|
$ledger_ids = array();
|
|
$data['Entry'] = array();
|
|
foreach ($data['Ledger'] AS $ledger) {
|
|
$ledger_id = $ledger['old_ledger_id'];
|
|
$new_ledger_id = $ledger['new_ledger_id'];
|
|
$amount = $ledger['amount'];
|
|
$account_id = $this->Account->Ledger->accountID($ledger_id);
|
|
$crdr = strtoupper($this->Account->fundamentalOpposite($account_id));
|
|
$comment = "Ledger Carry Forward (c/f)";
|
|
|
|
// Save the ledger ID for later, to mark it as closed
|
|
$ledger_ids[] = $ledger_id;
|
|
|
|
// No need to generate ledger entries if there is no balance
|
|
if (empty($ledger['amount']) || $ledger['amount'] == 0)
|
|
continue;
|
|
|
|
// Add an entry to carry the ledger balance forward
|
|
$data['Entry'][] = compact('account_id', 'ledger_id', 'new_ledger_id',
|
|
'crdr', 'amount', 'comment');
|
|
}
|
|
unset($data['Ledger']);
|
|
|
|
// Add the transaction and carry forward balances
|
|
$ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['close_id'] = $ids['transaction_id'];
|
|
|
|
// Mark the older ledgers as closed
|
|
if (!empty($ids['close_id'])) {
|
|
$this->LedgerEntry->Ledger->updateAll
|
|
(array('Ledger.close_transaction_id' => $ids['close_id']),
|
|
array('Ledger.id' => $ledger_ids)
|
|
);
|
|
}
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addRefund
|
|
* - Adds a new refund
|
|
*/
|
|
|
|
function addRefund($data, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => true,
|
|
);
|
|
|
|
// Establish the transaction as a Refund. This is just like a
|
|
// Payment, except instead of paying out of the account payable,
|
|
// it comes from the customer credit in the account receivable.
|
|
// Someday, perhaps we'll just issue a Credit Note or similar,
|
|
// but for now, a refund means it's time to actually PAY.
|
|
$data['Transaction'] +=
|
|
array('account_id' => $this->Account->accountReceivableAccountID());
|
|
|
|
// Also, to make it clear to the user, we flag as a REFUND
|
|
// even though that type works and operates just as PAYMENT
|
|
foreach ($data['Entry'] AS &$entry)
|
|
$entry += array('type' => 'REFUND');
|
|
|
|
$ids = $this->addPayment($data, $customer_id, $lease_id);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['refund_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addPayment
|
|
* - Adds a new payment transaction, which is money outflow
|
|
*/
|
|
|
|
function addPayment($data, $customer_id, $lease_id = null) {
|
|
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
|
|
|
// Set up control parameters
|
|
$data += array('control' => array());
|
|
$data['control'] +=
|
|
array('assign' => false,
|
|
'include_ledger_entry' => true,
|
|
'include_statement_entry' => true,
|
|
);
|
|
|
|
// Establish the transaction as an payment
|
|
$data['Transaction'] +=
|
|
array('type' => 'PAYMENT',
|
|
'crdr' => 'DEBIT',
|
|
'account_id' => $this->Account->accountPayableAccountID(),
|
|
'customer_id' => $customer_id,
|
|
'lease_id' => $lease_id,
|
|
);
|
|
|
|
// Go through the statement entries and flag as payments
|
|
foreach ($data['Entry'] AS &$entry)
|
|
$entry += array('type' => 'PAYMENT',
|
|
'crdr' => 'CREDIT',
|
|
);
|
|
|
|
$ids = $this->addTransaction($data['control'], $data['Transaction'], $data['Entry']);
|
|
if (isset($ids['transaction_id']))
|
|
$ids['payment_id'] = $ids['transaction_id'];
|
|
|
|
return $this->prReturn($ids);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: verifyTransaction
|
|
* - Verifies consistenty of new transaction data
|
|
* (not in a pre-existing transaction)
|
|
*/
|
|
function verifyTransaction($transaction, $entries) {
|
|
//$this->prFunctionLevel(10);
|
|
$this->prEnter(compact('transaction', 'entries'));
|
|
|
|
// Verify required Transaction data is present
|
|
if (empty($transaction['type']) ||
|
|
($transaction['type'] != 'CLOSE'
|
|
&& (empty($transaction['account_id']) ||
|
|
empty($transaction['crdr']))) ||
|
|
(in_array($transaction['type'], array('INVOICE', 'RECEIPT'))
|
|
&& empty($transaction['customer_id']))
|
|
) {
|
|
return $this->prReturn(false);
|
|
}
|
|
|
|
// Verify all entries
|
|
foreach ($entries AS $entry) {
|
|
// Ensure these items are null'ed out so we don't
|
|
// accidentally pick up stale data.
|
|
$le1 = $le1_tender = $le2 = $se = null;
|
|
extract($entry);
|
|
if (!empty($le1) && !empty($le2) &&
|
|
!$this->LedgerEntry->DoubleEntry->verifyDoubleEntry($le1, $le2, $le1_tender)) {
|
|
return $this->prReturn(false);
|
|
}
|
|
if (!empty($se) &&
|
|
!$this->StatementEntry->verifyStatementEntry($se)) {
|
|
return $this->prReturn(false);
|
|
}
|
|
}
|
|
|
|
return $this->prReturn(true);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addTransaction
|
|
* - Adds a new transaction, and the appropriate ledger and statement
|
|
* entries, as layed out in the $data['Entry'] array. The array is
|
|
* overloaded, since it is used to create both ledger _and_ statement
|
|
* entries.
|
|
*
|
|
* $data
|
|
* - Transaction
|
|
* - [MANDATORY]
|
|
* - type (INVOICE, RECEIPT)
|
|
* - account_id
|
|
* - crdr
|
|
* - [OPTIONAL]
|
|
* - stamp
|
|
* (default: NOW)
|
|
* - comment
|
|
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
|
* - id
|
|
* - amount
|
|
* - customer_id
|
|
* - ledger_id
|
|
*
|
|
* - Entry (array)
|
|
* - [MANDATORY]
|
|
* - type (CHARGE, DISBURSEMENT)
|
|
* - account_id
|
|
* - crdr
|
|
* - amount
|
|
* - [OPTIONAL]
|
|
* - effective_date
|
|
* - through_date
|
|
* - due_date
|
|
* - comment (used for statement or ledger entry, based on context)
|
|
* - ledger_entry_comment
|
|
* - statement_entry_comment
|
|
* - Tender
|
|
* - [MANDATORY]
|
|
* - tender_type_id
|
|
* - [OPTIONAL]
|
|
* - name
|
|
* (default: Entry Account Name & data1)
|
|
* - data1, data2, data3, data4
|
|
* - comment
|
|
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
|
* - id
|
|
* - ledger_entry_id
|
|
* - deposit_transaction_id
|
|
* - nsf_transaction_id
|
|
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
|
* - id
|
|
* - transaction_id
|
|
* - ledger_id
|
|
*
|
|
*/
|
|
|
|
function addTransaction($control, $transaction, $entries) {
|
|
$this->prEnter(compact('control', 'transaction', 'entries'));
|
|
|
|
// Verify that we have a transaction and entries
|
|
if (empty($transaction) ||
|
|
($transaction['type'] !== 'CLOSE' && empty($entries)))
|
|
return $this->prReturn(array('error' => true));
|
|
|
|
// set ledger ID as the current ledger of the specified account
|
|
if (empty($transaction['ledger_id']))
|
|
$transaction['ledger_id'] =
|
|
$this->Account->currentLedgerID($transaction['account_id']);
|
|
|
|
// Automatically figure out the customer if we have the lease
|
|
if (!empty($transaction['lease_id']) && empty($transaction['customer_id'])) {
|
|
$L = new Lease();
|
|
$L->id = $transaction['lease_id'];
|
|
$transaction['customer_id'] = $L->field('customer_id');
|
|
}
|
|
|
|
// Some transactions do not have their statement entries
|
|
// generated from this function, but they are instead
|
|
// created in the final steps during the reconciliation
|
|
// phase by the assignCredits function. Keep track of
|
|
// what type the statement entries _would_ have been, so
|
|
// that the assignCredits function can do the same.
|
|
$assign_disbursement_type = null;
|
|
|
|
// 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 ($entries AS &$entry) {
|
|
// Ensure these items are null'ed out so we don't
|
|
// accidentally pick up stale data.
|
|
$le1 = $le1_tender = $le2 = $se = null;
|
|
|
|
// Really, data should be sanitized at the controller,
|
|
// and not here. However, it's a one stop cleanup.
|
|
$entry['amount'] = str_replace('$', '', $entry['amount']);
|
|
|
|
// Set up our comments, possibly using the default 'comment' field
|
|
if (empty($entry['ledger_entry_comment'])) {
|
|
if ($transaction['type'] != 'INVOICE' && !empty($entry['comment']))
|
|
$entry['ledger_entry_comment'] = $entry['comment'];
|
|
else
|
|
$entry['ledger_entry_comment'] = null;
|
|
}
|
|
if (empty($entry['statement_entry_comment'])) {
|
|
if ($transaction['type'] == 'INVOICE' && !empty($entry['comment']))
|
|
$entry['statement_entry_comment'] = $entry['comment'];
|
|
else
|
|
$entry['statement_entry_comment'] = null;
|
|
}
|
|
|
|
if (!empty($control['include_ledger_entry']) || !empty($entry['include_ledger_entry'])) {
|
|
// Create one half of the Double Ledger Entry (and the Tender)
|
|
$le1 =
|
|
array_intersect_key($entry,
|
|
array_flip(array('ledger_id', '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
|
|
if ($transaction['type'] == 'CLOSE') {
|
|
$le2 =
|
|
array_intersect_key($entry,
|
|
array_flip(array('account_id', 'amount')));
|
|
$le2['ledger_id'] = $entry['new_ledger_id'];
|
|
$le2['crdr'] = strtoupper($this->Account->fundamentalOpposite($le1['crdr']));
|
|
$le2['comment'] = "Ledger Balance Forward (b/f)";
|
|
}
|
|
else {
|
|
$le2 =
|
|
array_intersect_key($entry,
|
|
array_flip(array('amount'))) +
|
|
array_intersect_key($transaction,
|
|
array_flip(array('ledger_id', 'account_id', 'crdr')));
|
|
}
|
|
}
|
|
else
|
|
$le1 = $le1_tender = $le2 = null;
|
|
|
|
// Now that the ledger entries are in place, respect the 'negative' flag
|
|
if (!empty($control['negative']))
|
|
$entry['amount'] *= -1;
|
|
|
|
if (!empty($control['include_statement_entry']) || !empty($entry['include_statement_entry'])) {
|
|
// Create the statement entry
|
|
$se =
|
|
array_intersect_key($entry,
|
|
array_flip(array('type', 'account_id', 'amount',
|
|
'effective_date', 'through_date', 'due_date',
|
|
'customer_id', 'lease_id',
|
|
'charge_entry_id'))) +
|
|
array_intersect_key($transaction,
|
|
array_flip(array('customer_id', 'lease_id')));
|
|
$se['comment'] = $entry['statement_entry_comment'];
|
|
}
|
|
else {
|
|
if (!empty($control['assign'])) {
|
|
if (empty($assign_disbursement_type))
|
|
$assign_disbursement_type = $entry['type'];
|
|
elseif ($entry['type'] != $assign_disbursement_type)
|
|
$this->INTERNAL_ERROR('Multiple disbursement types for this transaction');
|
|
}
|
|
|
|
$se = null;
|
|
}
|
|
|
|
// Add entry amount into the transaction total
|
|
$transaction['amount'] += $entry['amount'];
|
|
|
|
// Replace combined entry with our new individual entries
|
|
$entry = compact('le1', 'le1_tender', 'le2', 'se');
|
|
}
|
|
|
|
/* // Add a summary ledger entry if requested */
|
|
/* if (!empty($control['summary_double_entry'])) { */
|
|
/* $entry = $control['summary_double_entry']; */
|
|
|
|
/* $le1 = */
|
|
/* array_intersect_key($entry, */
|
|
/* array_flip(array('ledger_id', 'account_id', 'crdr', 'comment'))) + */
|
|
/* array_intersect_key($transaction, */
|
|
/* array_flip(array('amount'))); */
|
|
/* $le1_tender = isset($entry['Tender']) ? $entry['Tender'] : null; */
|
|
|
|
/* $le2 = */
|
|
/* array_intersect_key($transaction, */
|
|
/* array_flip(array('ledger_id', 'account_id', 'crdr'))); */
|
|
|
|
/* array_unshift($entries, */
|
|
/* array('le1' => array('account_id' => $de['le1']['account_id'], */
|
|
/* 'crdr' => strtoupper($this->Account->fundamentalOpposite */
|
|
/* ($control['ar_ledger_entry'])), */
|
|
/* 'amount' => -1 * $transaction['amount']), */
|
|
|
|
/* 'le2' => array('account_id' => $this->Account->accountReceivableAccountID(), */
|
|
/* 'crdr' => $control['ar_ledger_entry'], */
|
|
/* 'amount' => -1 * $transaction['amount']) */
|
|
/* )); */
|
|
/* } */
|
|
|
|
$this->pr(20, compact('transaction', 'entries'));
|
|
|
|
// Move forward, verifying and saving everything.
|
|
$ret = array();
|
|
if (!$this->verifyTransaction($transaction, $entries))
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
|
|
// Save transaction to the database
|
|
$this->create();
|
|
if (!$this->save($transaction))
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
$transaction_stamp = $this->field('stamp');
|
|
|
|
// Set up our return ids array
|
|
$ret['transaction_id'] = $this->id;
|
|
$ret['entries'] = array();
|
|
$ret['error'] = false;
|
|
|
|
// Go through the entries
|
|
foreach ($entries AS $e_index => &$entry) {
|
|
// Ensure these items are null'ed out so we don't
|
|
// accidentally pick up stale data.
|
|
$le1 = $le1_tender = $le2 = $se = null;
|
|
extract($entry);
|
|
|
|
if (!empty($le1) && !empty($le2)) {
|
|
$le1['transaction_id'] = $le2['transaction_id'] = $ret['transaction_id'];
|
|
if (isset($le1_tender))
|
|
$le1_tender['customer_id'] = $transaction['customer_id'];
|
|
$result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender);
|
|
$ret['entries'][$e_index]['DoubleEntry'] = $result;
|
|
if ($result['error']) {
|
|
$ret['error'] = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!empty($se)) {
|
|
$se['transaction_id'] = $ret['transaction_id'];
|
|
if (empty($se['effective_date']))
|
|
$se['effective_date'] = $transaction_stamp;
|
|
$result = $this->StatementEntry->addStatementEntry($se);
|
|
$ret['entries'][$e_index]['StatementEntry'] = $result;
|
|
if ($result['error']) {
|
|
$ret['error'] = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($control['assign']) && !$ret['error']) {
|
|
$result = $this->StatementEntry->assignCredits
|
|
(null,
|
|
(empty($control['assign_receipt']) ? null
|
|
: $ret['transaction_id']),
|
|
null,
|
|
$assign_disbursement_type,
|
|
$transaction['customer_id'],
|
|
$transaction['lease_id']
|
|
);
|
|
|
|
$ret['assigned'] = $result;
|
|
if ($result['error'])
|
|
$ret['error'] = true;
|
|
}
|
|
|
|
$this->Customer->update($transaction['customer_id']);
|
|
return $this->prReturn($ret);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: addNsf
|
|
* - Adds NSF transaction
|
|
*/
|
|
|
|
function addNsf($tender, $stamp = null, $comment = null) {
|
|
$this->prEnter(compact('tender', 'stamp', 'comment'));
|
|
|
|
$ret = array();
|
|
|
|
// Enter the NSF
|
|
// This is the transaction pulling money from the bank account
|
|
// and recording it in the NSF account. It has nothing to do
|
|
// with the customer statement (charges, disbursements, credits, etc).
|
|
$bounce_result = $this->addDeposit
|
|
(array('Transaction' =>
|
|
array('stamp' => $stamp,
|
|
'type' => 'WITHDRAWAL',
|
|
'crdr' => 'CREDIT'),
|
|
|
|
'Entry' =>
|
|
array(array('tender_id' => null,
|
|
'account_id' => $this->Account->nsfAccountID(),
|
|
'crdr' => 'DEBIT',
|
|
'amount' => $tender['LedgerEntry']['amount'],
|
|
))),
|
|
$tender['Transaction']['account_id']);
|
|
|
|
$this->pr(20, compact('bounce_result'));
|
|
$ret['bounce'] = $bounce_result;
|
|
if ($bounce_result['error'])
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
|
|
// Since we may have saved the nsf transaction with a null
|
|
// timestamp, query it back out of the database to find out
|
|
// what timestamp was _really_ specified, for later use.
|
|
$bounce = $this->find
|
|
('first', array('contain' => false, 'id' => $bounce_result['transaction_id']));
|
|
$this->pr(20, compact('bounce'));
|
|
$stamp = $bounce['Transaction']['stamp'];
|
|
|
|
// OK, now move into customer realm, finding all statement
|
|
// entries that were affected by the bad payment (tender).
|
|
$nsf_ledger_entry = $this->LedgerEntry->find
|
|
('first', array
|
|
('contain' => array('Transaction' =>
|
|
array(//'fields' => array(),
|
|
'StatementEntry' =>
|
|
array(//'fields' => array(),
|
|
),
|
|
),
|
|
),
|
|
'conditions' => array('LedgerEntry.id' => $tender['LedgerEntry']['id']),
|
|
));
|
|
|
|
$this->pr(20, compact('nsf_ledger_entry'));
|
|
if (!$nsf_ledger_entry)
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
|
|
// Build a transaction to adjust all of the statement entries
|
|
$rollback =
|
|
array('control' =>
|
|
array('assign' => false,
|
|
'include_ledger_entry' => false,
|
|
'include_statement_entry' => true,
|
|
),
|
|
|
|
'Transaction' =>
|
|
array('stamp' => $stamp,
|
|
'type' => 'RECEIPT',
|
|
'crdr' => 'CREDIT',
|
|
'account_id' => $this->Account->nsfAccountID(),
|
|
'customer_id' => $tender['Tender']['customer_id'],
|
|
'comment' => $comment,
|
|
),
|
|
|
|
'Entry' => array());
|
|
|
|
$summary_amount = 0;
|
|
foreach ($nsf_ledger_entry['Transaction']['StatementEntry'] AS $disbursement) {
|
|
if ($disbursement['type'] === 'SURPLUS') {
|
|
$disbursement['type'] = 'VOID';
|
|
$this->StatementEntry->id = $disbursement['id'];
|
|
$this->StatementEntry->saveField('type', $disbursement['type']);
|
|
}
|
|
else {
|
|
$rollback['Entry'][] =
|
|
array('type' => $disbursement['type'],
|
|
'amount' => -1 * $disbursement['amount'],
|
|
'account_id' => $this->Account->nsfAccountID(),
|
|
'customer_id' => $disbursement['customer_id'],
|
|
'lease_id' => $disbursement['lease_id'],
|
|
'charge_entry_id' => $disbursement['charge_entry_id'],
|
|
);
|
|
$summary_amount += $disbursement['amount'];
|
|
}
|
|
}
|
|
|
|
// Add the sole ledger entry for this transaction
|
|
$rollback['Entry'][] =
|
|
array('include_ledger_entry' => true,
|
|
'include_statement_entry' => false,
|
|
'amount' => $summary_amount,
|
|
'account_id' => $this->Account->accountReceivableAccountID(),
|
|
'crdr' => 'DEBIT',
|
|
);
|
|
|
|
// Record the transaction, which will un-pay previously paid
|
|
// charges, void any credits, and other similar work.
|
|
if (count($rollback['Entry'])) {
|
|
$rollback_result = $this->addTransaction($rollback['control'],
|
|
$rollback['Transaction'],
|
|
$rollback['Entry']);
|
|
$this->pr(20, compact('rollback', 'rollback_result'));
|
|
$ret['rollback'] = $rollback_result;
|
|
if ($rollback_result['error'])
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
}
|
|
|
|
// Add NSF Charge
|
|
$charge_result = $this->addInvoice
|
|
(array('Transaction' => compact('stamp'),
|
|
|
|
'Entry' =>
|
|
array
|
|
(array('account_id' => $this->Account->nsfChargeAccountID(),
|
|
'effective_date' => $stamp,
|
|
// REVISIT <AP>: 20090730
|
|
// BAD, BAD, BAD... who would actually
|
|
// hardcode a value like this???? ;-)
|
|
'amount' => 35,
|
|
'comment' => "NSF: " . $tender['Tender']['name'],
|
|
),
|
|
),
|
|
),
|
|
$tender['Tender']['customer_id']);
|
|
|
|
$this->pr(20, compact('charge_result'));
|
|
$ret['charge'] = $charge_result;
|
|
if ($charge_result['error'])
|
|
return $this->prReturn(array('error' => true) + $ret);
|
|
|
|
$ret['nsf_transaction_id'] = $ret['bounce']['transaction_id'];
|
|
if (!empty($ret['rollback']))
|
|
$ret['nsf_ledger_entry_id'] = $ret['rollback']['entries'][0]['DoubleEntry']['Entry1']['ledger_entry_id'];
|
|
return $this->prReturn($ret + array('error' => false));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: stats
|
|
* - Deletes a transaction and associated entries
|
|
* - !!WARNING!! This should be used with EXTREME caution, as it
|
|
* irreversibly destroys the data. It is not for normal use,
|
|
* and can leave the database in an inconsistent state. Expected
|
|
* scenario is to remove a bad transaction directly after creation,
|
|
* before it gets tied to anything else in the system (such as
|
|
* a late charge invoice for a customer that isn't actually late).
|
|
*/
|
|
|
|
function destroy($id) {
|
|
$this->prFunctionLevel(30);
|
|
$this->prEnter(compact('id'));
|
|
/* $transaction = $this->find */
|
|
/* ('first', */
|
|
/* array('contain' => */
|
|
/* array(// Models */
|
|
/* 'StatementEntry', */
|
|
/* 'LedgerEntry' => array('Tender'), */
|
|
/* ), */
|
|
/* 'conditions' => array(array('Transaction.id' => $id)), */
|
|
/* )); */
|
|
/* pr($transaction); */
|
|
|
|
$this->id = $id;
|
|
$customer_id = $this->field('customer_id');
|
|
$result = $this->delete($id);
|
|
|
|
if (!empty($customer_id)) {
|
|
$this->StatementEntry->assignCredits
|
|
(null, null, null, null, $customer_id, null);
|
|
//$this->Customer->update($customer_id);
|
|
}
|
|
|
|
return $this->prReturn($result);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: update
|
|
* - Update any cached or calculated fields
|
|
*/
|
|
function update($id) {
|
|
$this->INTERNAL_ERROR("Transaction::update not yet implemented");
|
|
|
|
$result = $this->find
|
|
('first',
|
|
array('link' => array('StatementEntry'),
|
|
'fields' => array("SUM(LedgerEntry.amount) AS total"),
|
|
'conditions' => array(array('LedgerEntry.account_id = Transaction.account_id'),
|
|
),
|
|
));
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
**************************************************************************
|
|
**************************************************************************
|
|
* function: stats
|
|
* - Returns summary data from the requested transaction
|
|
*/
|
|
function stats($id = null, $query = null, $balance_account_id = null) {
|
|
$this->prEnter(compact('id', 'query'));
|
|
|
|
$this->queryInit($query);
|
|
unset($query['group']);
|
|
|
|
if (isset($id)) {
|
|
$query['conditions'][] = array('Transaction.id' => $id);
|
|
$query['group'] = 'Transaction.id';
|
|
}
|
|
else
|
|
// CakePHP seems to automagically add in our ID as a part
|
|
// of the query conditions, but only on a 'first' query,
|
|
// not an 'all'. I suppose this is helpful :-/
|
|
unset($this->id);
|
|
|
|
if (empty($query['fields']))
|
|
$query['fields'] = array();
|
|
|
|
$stats = array();
|
|
foreach ($this->hasMany AS $table => $association) {
|
|
// Only calculate stats for *Entry types
|
|
if (!preg_match("/Entry$/", $table) &&
|
|
!preg_match("/Entry$/", $association['className']))
|
|
continue;
|
|
|
|
$squery = $query;
|
|
$squery['link'][$table] = array('fields' => array());
|
|
|
|
if ($table == 'LedgerEntry') {
|
|
if (isset($balance_account_id)) {
|
|
$squery['link']['LedgerEntry']['Account'] = array('fields' => array());
|
|
$squery['conditions'][] = array("Account.id" => $balance_account_id);
|
|
}
|
|
|
|
$squery['fields'] = array_merge($squery['fields'],
|
|
$this->LedgerEntry->debitCreditFields(true, $balance_account_id != null));
|
|
}
|
|
elseif ($table == 'StatementEntry') {
|
|
$squery['fields'] = array_merge($squery['fields'],
|
|
$this->StatementEntry->chargeDisbursementFields(true));
|
|
}
|
|
else {
|
|
$squery['fields'][] = "SUM({$table}.amount) AS total";
|
|
$squery['fields'][] = "COUNT({$table}.id) AS entries";
|
|
}
|
|
$stats[$table] = $this->find('first', $squery);
|
|
// REVISIT <AP>: 20090724
|
|
// [0][0] is for when we do an 'all' query. This can
|
|
// be removed at some point, but I'm keeping it while
|
|
// toggling between 'all' and 'first' (testing).
|
|
if (isset($stats[$table][0][0]))
|
|
$stats[$table] += $stats[$table][0][0];
|
|
else
|
|
$stats[$table] += $stats[$table][0];
|
|
unset($stats[$table][0]);
|
|
}
|
|
|
|
return $this->prReturn($stats);
|
|
}
|
|
}
|
|
?>
|