Compare commits

..

6 Commits

Author SHA1 Message Date
abijah
0b69e065ae Major change to invoice/receipt algorithms. The end result shouldn't be visible, but there were significant problems understanding how to reconcile entries and this cleaned things up significantly. There may be some bugs left, but quick tests show it to be working
git-svn-id: file:///svn-source/pmgr/branches/charge_credit_20090629@190 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-30 17:52:43 +00:00
abijah
1c8cc70a28 Moving back to the Invoice/Receipt mechanism. Seems to work fairly well, even if it's non-standard.
git-svn-id: file:///svn-source/pmgr/branches/charge_credit_20090629@189 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-30 04:47:18 +00:00
abijah
c6f68861b3 Branch to revert way way back and get down to simple items, even if it's not extensible.
git-svn-id: file:///svn-source/pmgr/branches/charge_credit_20090629@188 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-30 01:06:33 +00:00
abijah
ce4fa4131a More changes. I just can't seem to come up with a solution that works that I like. The problem now, without invoice/receipt, is that one check cannot cleanly pay for two units.
git-svn-id: file:///svn-source/pmgr/branches/single_AR_20090622@182 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-24 00:33:39 +00:00
abijah
80f8bd36d5 Nowhere near done yet, but checking in a snapshot of semi-working code. There is some simultaneous support for both with and without use of the Invoice/Receipt account. I want to do away with them completely, but will need to change how sitelink payments are mapped (right now, they split a payment into multiple parts to match the charge).
git-svn-id: file:///svn-source/pmgr/branches/single_AR_20090622@181 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-23 19:04:41 +00:00
abijah
7aa1100cac Moving to the standard accounting method of a single Account Receiveable.
git-svn-id: file:///svn-source/pmgr/branches/single_AR_20090622@180 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-06-22 23:46:03 +00:00
19 changed files with 987 additions and 604 deletions

View File

@@ -633,15 +633,6 @@ CREATE TABLE `pmgr_customers` (
-- If NULL, rely on the contact info exclusively
`name` VARCHAR(80) DEFAULT NULL,
-- A customer gets their own account, although any
-- lease specific charge (rent, late fees, etc) will
-- be debited against the lease account. This one
-- will be used for non-lease related charges, as
-- well as charges that intentionally span more than
-- one lease (such as one security deposit to cover
-- two or more units).
`account_id` INT(10) UNSIGNED NOT NULL,
-- Primary Contact... every customer must have one
-- (and presumably, most customers will _be_ one).
-- REVISIT <AP> 20090612:
@@ -708,7 +699,6 @@ CREATE TABLE `pmgr_leases` (
`lease_type_id` INT(10) UNSIGNED NOT NULL,
`unit_id` INT(10) UNSIGNED NOT NULL,
`customer_id` INT(10) UNSIGNED NOT NULL,
`account_id` INT(10) UNSIGNED NOT NULL,
`late_schedule_id` INT(10) UNSIGNED DEFAULT NULL,
`lease_date` DATE NOT NULL,
@@ -848,7 +838,7 @@ CREATE TABLE `pmgr_accounts` (
-- For LIABILITY, EQUITY, and INCOME, the opposite
-- is true, with reconciliations posted, under
-- normal circumstances, when a debit occurs.
`trackable` INT UNSIGNED DEFAULT 0,
`trackable` INT UNSIGNED DEFAULT 1,
-- Security Level
`level` INT UNSIGNED DEFAULT 1,
@@ -866,15 +856,20 @@ LOCK TABLES `pmgr_accounts` WRITE;
INSERT INTO `pmgr_accounts` (`type`, `name`, `trackable`)
VALUES
('ASSET', 'A/R', 1),
('ASSET', 'Invoice', 1),
('ASSET', 'Receipt', 1),
('LIABILITY', 'A/P', 1),
('LIABILITY', 'Tax', 0),
('LIABILITY', 'Tax', 1),
('LIABILITY', 'Customer Credit', 1),
('ASSET', 'Bank', 0),
('ASSET', 'Payment', 0),
('ASSET', 'Bank', 1),
('ASSET', 'Cash', 1),
('ASSET', 'Check', 1),
('ASSET', 'Money Order', 1),
('LIABILITY', 'Security Deposit', 1),
('INCOME', 'Rent', 0),
('INCOME', 'Late Charge', 0),
('EXPENSE', 'Concession', 0);
('INCOME', 'Rent', 1),
('INCOME', 'Late Charge', 1),
('EXPENSE', 'Concession', 1),
('EXPENSE', 'Bad Debt', 1);
UNLOCK TABLES;
@@ -939,6 +934,10 @@ DROP TABLE IF EXISTS `pmgr_transactions`;
CREATE TABLE `pmgr_transactions` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `type` ENUM('INVOICE',
-- 'RECEIPT')
-- NOT NULL,
`stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`through_date` DATE DEFAULT NULL,
`due_date` DATE DEFAULT NULL,
@@ -965,6 +964,8 @@ CREATE TABLE `pmgr_ledger_entries` (
`name` VARCHAR(80) DEFAULT NULL,
`monetary_source_id` INT(10) UNSIGNED DEFAULT NULL, -- NULL if internal transfer
`transaction_id` INT(10) UNSIGNED NOT NULL,
`customer_id` INT(10) UNSIGNED DEFAULT NULL,
`lease_id` INT(10) UNSIGNED DEFAULT NULL,
`amount` FLOAT(12,2) NOT NULL,
`debit_ledger_id` INT(10) UNSIGNED NOT NULL,
@@ -986,8 +987,7 @@ CREATE TABLE `pmgr_reconciliations` (
`debit_ledger_entry_id` INT(10) UNSIGNED NOT NULL,
`credit_ledger_entry_id` INT(10) UNSIGNED NOT NULL,
`terminal_ledger_entry_id` INT(10) UNSIGNED DEFAULT NULL,
`amount` FLOAT(12,2) NOT NULL,
`amount` FLOAT(12,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

File diff suppressed because it is too large Load Diff

View File

@@ -305,7 +305,13 @@ class AppController extends Controller {
$id = 'id';
extract(array_intersect_key($fields, array_flip($special)));
foreach ($records AS &$record) {
if (!isset($record[$table]))
continue;
foreach (array_diff_key($fields, array_flip($special)) AS $field) {
if (!isset($record[$table][$id]) || !isset($record[$table][$field]))
continue;
// DEBUG PURPOSES ONLY!
//$params['linkrecord'][] = compact('table', 'field', 'id', 'controller', 'record');
$record[$table][$field] =
@@ -338,30 +344,40 @@ class AppController extends Controller {
}
function jqGridDataOutputRecords(&$params, &$model, &$records) {
$model_alias = $model->alias;
$id = 'jqGrid_id';
$id_field = 'jqGrid_id';
foreach ($records AS $record) {
echo " <row id='{$record[$id]}'>\n";
foreach ($params['fields'] AS $field) {
if (preg_match("/\./", $field)) {
list($tbl, $col) = explode(".", $field);
$data = $record[$tbl][$col];
}
else {
$data = $record[$model_alias][$field];
}
// be sure to put text data in CDATA
if (preg_match("/^\d*$/", $data))
echo " <cell>$data</cell>\n";
else
echo " <cell><![CDATA[$data]]></cell>\n";
}
echo " </row>\n";
$this->jqGridDataOutputRecord($params, $model, $record,
$record[$id_field], $params['fields']);
}
}
function jqGridDataOutputRecord(&$params, &$model, &$record, $id, $fields) {
echo " <row id='$id'>\n";
foreach ($fields AS $field) {
$this->jqGridDataOutputRecordField($params, $model, $record, $field);
}
echo " </row>\n";
}
function jqGridDataOutputRecordField(&$params, &$model, &$record, $field) {
if (preg_match("/\./", $field)) {
list($tbl, $col) = explode(".", $field);
$data = $record[$tbl][$col];
}
else {
$data = $record[$model->alias][$field];
}
$this->jqGridDataOutputRecordCell($params, $model, $record, $field, $data);
}
function jqGridDataOutputRecordCell(&$params, &$model, &$record, $field, $data) {
// be sure to put text data in CDATA
if (preg_match("/^\d*$/", $data))
echo " <cell>$data</cell>\n";
else
echo " <cell><![CDATA[$data]]></cell>\n";
}
function jqGridDataFinalize(&$params) {
if ($params['debug']) {
$xml = ob_get_contents();

View File

@@ -71,6 +71,7 @@ class LeasesController extends AppController {
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
$links['Lease'] = array('number');
$links['Unit'] = array('name');
$links['Customer'] = array('name');
return parent::jqGridRecordLinks($params, $model, $records, $links);
}
@@ -107,7 +108,6 @@ class LeasesController extends AppController {
array(// Models
'LeaseType',
'Unit',
'Account' => array('CurrentLedger'),
'Customer',
),
'conditions' => array(array('Lease.id' => $id)),
@@ -115,23 +115,15 @@ class LeasesController extends AppController {
)
);
// Summarize each ledger
$this->Lease->statsMerge($lease,
$this->Lease->stats($lease['Lease']['id']));
// Obtain the overall lease balance
$this->Lease->statsMerge($lease['Lease'],
array('stats' => $this->Lease->stats($id)));
$outstanding_balance = $lease['Lease']['stats']['Account']['Ledger']['balance'];
$outstanding_balance = $lease['Lease']['stats']['balance'];
// Determine the lease security deposit
$deposits = $this->Lease->findSecurityDeposits($lease['Lease']['id']);
$outstanding_deposit = $deposits['summary']['balance'];
// Move the Leder stats into our alias 'CurrentLedger'
$lease['Account']['CurrentLedger'] += $lease['Account']['Ledger'];
unset($lease['Account']['Ledger']);
// Prepare to render
$title = 'Lease: #' . $lease['Lease']['id'];
$this->set(compact('lease', 'title',

View File

@@ -25,6 +25,14 @@ class LedgerEntriesController extends AppController {
* to jqGrid.
*/
function jqGridDataSetup(&$params) {
parent::jqGridDataSetup($params);
if (isset($params['custom']['ar_account'])) {
$params['custom']['account_id'] =
$this->LedgerEntry->DebitLedger->Account->accountReceivableAccountID();
}
}
function jqGridDataTables(&$params, &$model) {
$link =
array(// Models
@@ -36,6 +44,16 @@ class LedgerEntriesController extends AppController {
array('fields' => array('id', 'name'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Lease' =>
array('fields' => array('id', 'number'),
'Unit' =>
array('fields' => array('id', 'name'),
),
),
);
if (isset($params['custom']['account_ftype'])) {
@@ -77,6 +95,19 @@ class LedgerEntriesController extends AppController {
);
}
if (isset($params['custom']['account_id'])) {
$account_id = $params['custom']['account_id'];
$link['Ledger'] =
array('fields' => array('id', 'sequence'),
'conditions' => ("Ledger.id = IF(DebitLedger.account_id = $account_id," .
" LedgerEntry.credit_ledger_id," .
" LedgerEntry.debit_ledger_id)"),
'Account' => array(
'fields' => array('id', 'name'),
),
);
}
if (isset($params['custom']['reconcile_id'])) {
$ftype = $params['custom']['account_ftype'];
$ftype = $this->LedgerEntry->DebitLedger->Account->fundamentalOpposite($ftype);
@@ -92,11 +123,14 @@ class LedgerEntriesController extends AppController {
$ledger_id = (isset($params['custom']['ledger_id'])
? $params['custom']['ledger_id']
: null);
$account_id = (isset($params['custom']['account_id'])
? $params['custom']['account_id']
: null);
$account_type = (isset($params['custom']['account_type'])
? $params['custom']['account_type']
: null);
return $model->ledgerContextFields2($ledger_id, $account_type);
return $model->ledgerContextFields2($ledger_id, $account_id, $account_type);
}
function jqGridDataConditions(&$params, &$model) {
@@ -118,20 +152,71 @@ class LedgerEntriesController extends AppController {
$conditions[] = array('Reconciliation.'.$ftype.'_ledger_entry_id' => $params['custom']['reconcile_id']);
}
if (isset($params['custom']['account_id'])) {
$conditions[] =
array('OR' =>
array(array('CreditAccount.id' => $params['custom']['account_id']),
array('DebitAccount.id' => $params['custom']['account_id'])));
}
if (isset($params['custom']['customer_id'])) {
$conditions[] =
array('Customer.id' => $params['custom']['customer_id']);
/* $Account = new Account(); */
/* if (isset($params['custom']['account_ftype']) || */
/* isset($params['custom']['ledger_id'])) { */
/* $conditions[] = */
/* array('OR' => array('Account.id' => $Account->invoiceAccountID(), */
/* 'Account.id' => $Account->receiptAccountID())); */
/* } else { */
/* $conditions[] = */
/* array('OR' => array('DebitAccount.id' => $Account->invoiceAccountID(), */
/* //'CreditAccount.id' => $Account->invoiceAccountID(), */
/* //'DebitAccount.id' => $Account->receiptAccountID(), */
/* 'CreditAccount.id' => $Account->receiptAccountID(), */
/* )); */
/* } */
}
if (isset($params['custom']['lease_id'])) {
$conditions[] =
array('Lease.id' => $params['custom']['lease_id']);
/* $Account = new Account(); */
/* if (isset($params['custom']['account_ftype']) || */
/* isset($params['custom']['ledger_id'])) { */
/* $conditions[] = */
/* array('OR' => array('Account.id' => $Account->invoiceAccountID(), */
/* 'Account.id' => $Account->receiptAccountID())); */
/* } else { */
/* $conditions[] = */
/* array('OR' => array('DebitAccount.id' => $Account->invoiceAccountID(), */
/* //'CreditAccount.id' => $Account->invoiceAccountID(), */
/* //'DebitAccount.id' => $Account->receiptAccountID(), */
/* 'CreditAccount.id' => $Account->receiptAccountID(), */
/* )); */
/* } */
}
if (isset($params['custom']['transaction_id'])) {
$conditions[] =
array('Transaction.id' => $params['custom']['transaction_id']);
}
return $conditions;
}
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
$links['Transaction'] = array('id');
$links['LedgerEntry'] = array('id');
if (isset($params['custom']['account_ftype']) || isset($params['custom']['ledger_id'])) {
$links['Account'] = array('controller' => 'accounts', 'name');
}
else {
$links['DebitAccount'] = array('controller' => 'accounts', 'name');
$links['CreditAccount'] = array('controller' => 'accounts', 'name');
}
$links['Account'] = array('controller' => 'accounts', 'name');
$links['DebitAccount'] = array('controller' => 'accounts', 'name');
$links['CreditAccount'] = array('controller' => 'accounts', 'name');
$links['MonetarySource'] = array('name');
$links['Customer'] = array('name');
$links['Lease'] = array('number');
$links['Unit'] = array('name');
return parent::jqGridRecordLinks($params, $model, $records, $links);
}
@@ -145,8 +230,39 @@ class LedgerEntriesController extends AppController {
function jqGridDataOrder(&$params, &$model, $index, $direction) {
/* if ($index === 'balance') */
/* return ($index .' '. $direction); */
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
return parent::jqGridDataOrder($params, $model, $index, $direction);
if ($index === 'Transaction.stamp') {
$order[] = 'LedgerEntry.id ' . $direction;
}
return $order;
}
function jqGridRecordsPostProcess(&$params, &$model, &$records) {
parent::jqGridRecordsPostProcess($params, $model, $records);
$subtotal = 0;
foreach ($records AS &$record) {
$amount = (isset($record['LedgerEntry']['balance'])
? $record['LedgerEntry']['balance']
: $record['LedgerEntry']['amount']);
$record['LedgerEntry']['subtotal'] = ($subtotal += $amount);
continue;
// Experiment to minimize columns by putting the monetary source
// as the Account, when available
if ($record['MonetarySource']['name'])
$record['Account']['name'] = $record['MonetarySource']['name'];
}
}
function jqGridDataOutputRecordCell(&$params, &$model, &$record, $field, $data) {
/* if ($field === 'CreditAccount.name') { */
/* $data .= '-OK'; */
/* } */
parent::jqGridDataOutputRecordCell($params, $model, $record, $field, $data);
}
@@ -167,6 +283,7 @@ class LedgerEntriesController extends AppController {
$entry = $this->LedgerEntry->find
('first',
array('contain' => array('MonetarySource.id',
'MonetarySource.name',
'MonetarySource.MonetaryType.id',
'Transaction.id',
'Transaction.stamp',
@@ -176,6 +293,9 @@ class LedgerEntriesController extends AppController {
'CreditLedger.id',
'CreditLedger.sequence',
'CreditLedger.account_id',
'Customer.id',
'Customer.name',
'Lease.id',
),
'fields' => array('LedgerEntry.id',
@@ -184,6 +304,7 @@ class LedgerEntriesController extends AppController {
'conditions' => array('LedgerEntry.id' => $id),
));
//pr($entry);
// Because 'DebitLedger' and 'CreditLedger' both relate to 'Account',
// CakePHP will not include them in the LedgerEntry->find (or so it

View File

@@ -121,7 +121,7 @@ class UnitsController extends AppController {
// Get the balance on each lease.
foreach ($unit['Lease'] AS &$lease) {
$stats = $this->Unit->Lease->stats($lease['id']);
$lease['balance'] = $stats['Account']['Ledger']['balance'];
$lease['balance'] = $stats['balance'];
}
$outstanding_balance = 0;
@@ -130,7 +130,7 @@ class UnitsController extends AppController {
// Figure out the outstanding balance of the current lease.
$stats = $this->Unit->stats($id);
$outstanding_balance =
$stats['CurrentLease']['Account']['Ledger']['balance'];
$stats['CurrentLease']['balance'];
// Figure out the total security deposit for the current lease.
$deposits = $this->Unit->Lease->findSecurityDeposits($unit['CurrentLease']['id']);

View File

@@ -82,37 +82,26 @@ class Account extends AppModel {
/**************************************************************************
**************************************************************************
**************************************************************************
* function: securityDepositAccountID
* - Returns the ID of the Security Deposit Account
* function: accountNameToID
* - Returns the ID of the named account
*/
function securityDepositAccountID() {
function accountNameToID($name) {
$this->cacheQueries = true;
$account = $this->find('first', array
('recursive' => -1,
'conditions' => array
(array('name' => 'Security Deposit')),
'conditions' => compact('name'),
));
$this->cacheQueries = false;
return $account['Account']['id'];
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function:rentAccountID
* - Returns the ID of the Rent Account
*/
function rentAccountID() {
$this->cacheQueries = true;
$account = $this->find('first', array
('recursive' => -1,
'conditions' => array
(array('name' => 'Rent')),
));
$this->cacheQueries = false;
return $account['Account']['id'];
}
function securityDepositAccountID() { return $this->accountNameToID('Security Deposit'); }
function rentAccountID() { return $this->accountNameToID('Rent'); }
function accountReceivableAccountID() { return $this->accountNameToID('A/R'); }
function invoiceAccountID() { return $this->accountReceivableAccountID(); }
function receiptAccountID() { return $this->accountReceivableAccountID(); }
//function invoiceAccountID() { return $this->accountNameToID('Invoice'); }
//function receiptAccountID() { return $this->accountNameToID('Receipt'); }
/**************************************************************************
@@ -122,22 +111,19 @@ class Account extends AppModel {
* - Returns an array of ledger ids from the given account
*/
function ledgers($id, $all = false) {
$cachekey = $all ? 'all' : 'current';
if (isset($this->cache[$id]['ledgers'][$cachekey])) {
return $this->cache[$id]['ledgers'][$cachekey];
}
if ($all) {
$contain = array('Ledger' => array('fields' => array('Ledger.id')));
} else {
$contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id')));
}
$this->cacheQueries = true;
$account = $this->find('first', array
('contain' => $contain,
'fields' => array(),
'conditions' => array(array('Account.id' => $id)),
));
$this->cacheQueries = false;
if ($all) {
$ledger_ids = array();
@@ -148,14 +134,11 @@ class Account extends AppModel {
$ledger_ids = array($account['CurrentLedger']['id']);
}
// Save the ledgers in our cache for future reference
$this->cache[$id]['ledgers'][$cachekey] = $ledger_ids;
/* pr(array('function' => 'Account::ledgers', */
/* 'args' => compact('id', 'all'), */
/* 'return' => $this->cache[$id]['ledgers'][$cachekey])); */
/* 'return' => $ledger_ids)); */
return $this->cache[$id]['ledgers'][$cachekey];
return $ledger_ids;
}
@@ -234,7 +217,11 @@ class Account extends AppModel {
* (such as charges not paid).
*/
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null, $cond = null) {
if (!isset($cond))
$cond = array();
$cond[] = array('Account.id' => $id);
foreach (($fundamental_type
? array($fundamental_type)
: array('debit', 'credit')) AS $fund) {
@@ -260,7 +247,7 @@ class Account extends AppModel {
'group' => ("LedgerEntry.id" .
" HAVING LedgerEntry.amount" .
" <> COALESCE(SUM(Reconciliation.amount),0)"),
'conditions' => array('Account.id' => $id),
'conditions' => $cond,
'fields' => array(),
));
$balance = 0;
@@ -290,7 +277,7 @@ class Account extends AppModel {
* whatever algorithm is simplest.
*/
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
function reconcileNewLedgerEntry($id, $fundamental_type, $amount, $cond = null) {
$ofund = $this->fundamentalOpposite($fundamental_type);
$unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0));
$applied = 0;
@@ -298,7 +285,7 @@ class Account extends AppModel {
// if there is no money in the entry, it can reconcile nothing
// don't bother wasting time sifting ledger entries.
if ($amount > 0) {
$unreconciled = $this->findUnreconciledLedgerEntries($id, $ofund);
$unreconciled = $this->findUnreconciledLedgerEntries($id, $ofund, $cond);
foreach ($unreconciled[$ofund]['entry'] AS $i => &$entry) {
// Determine if amount is sufficient to cover the entry

View File

@@ -8,7 +8,6 @@ class Customer extends AppModel {
);
var $belongsTo = array(
'Account',
'PrimaryContact' => array(
'className' => 'Contact',
),
@@ -20,11 +19,19 @@ class Customer extends AppModel {
'conditions' => 'CurrentLease.close_date IS NULL',
),
'Lease',
'Transaction',
'LedgerEntry',
// Cheat to get Account set as part of this class
'Account',
);
var $hasAndBelongsToMany = array(
'Contact',
'Transaction' => array(
'joinTable' => 'ledger_entries',
'foreignKey' => 'customer_id',
'associationForeignKey' => 'transaction_id',
),
);
@@ -80,16 +87,9 @@ class Customer extends AppModel {
/* )); */
$entries = $this->Account->findLedgerEntriesRelatedToAccount
($this->accountId($id),
($this->Account->invoiceAccountID(),
$this->Account->securityDepositAccountID(),
true, null, $link);
foreach ($this->leaseIds($id) AS $lease_id) {
$ledger_entries = $this->Lease->findSecurityDeposits($lease_id, $link);
$this->statsMerge($entries['summary'], $ledger_entries['summary']);
$entries['Entries'] = array_merge($entries['Entries'],
$ledger_entries['Entries']);
}
true, array('LedgerEntry.customer_id' => $id), $link);
/* pr(array('function' => 'Customer::findSecurityDeposits', */
/* 'args' => compact('id', 'link'), */
@@ -110,22 +110,10 @@ class Customer extends AppModel {
*/
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
$unreconciled = $this->Account->findUnreconciledLedgerEntries
($this->accountId($id), $fundamental_type);
foreach ($this->leaseIds($id) AS $lease_id) {
$unrec = $this->Lease->findUnreconciledLedgerEntries($lease_id,
$fundamental_type);
foreach (array_keys($unreconciled) AS $type) {
$left = &$unreconciled[$type];
$right = &$unrec[$type];
$left['entry'] = array_merge($left['entry'], $right['entry']);
$left['balance'] += $right['balance'];
}
}
($this->Account->accountReceivableAccountID(),
$fundamental_type,
array('LedgerEntry.customer_id' => $id));
return $unreconciled;
}
@@ -146,25 +134,11 @@ class Customer extends AppModel {
*/
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
$reconciled = $this->Account->reconcileNewLedgerEntry
($this->accountId($id), $fundamental_type, $amount);
foreach ($this->leaseIds($id) AS $lease_id) {
foreach (array_keys($reconciled) AS $type) {
$rec = $this->Lease->reconcileNewLedgerEntry($lease_id,
$fundamental_type,
$reconciled[$type]['unapplied']);
$left = &$reconciled[$type];
$right = &$rec[$type];
$left['entry'] = array_merge($left['entry'], $right['entry']);
$left['balance'] += $right['balance'];
$left['applied'] += $right['applied'];
$left['unapplied'] = $right['unapplied'];
}
}
($this->Account->accountReceivableAccountID(),
$fundamental_type,
$amount,
array('LedgerEntry.customer_id' => $id));
return $reconciled;
}
@@ -189,7 +163,6 @@ class Customer extends AppModel {
'ContactEmail',
'ContactAddress',
),
'Account',
'Lease' =>
array('Unit' =>
array('order' => array('sort_order'),
@@ -201,22 +174,12 @@ class Customer extends AppModel {
'conditions' => array('Customer.id' => $id),
));
// Add the lease balance to each lease.
foreach ($customer['Lease'] AS &$lease) {
$stats = $this->Lease->stats($lease['id']);
$lease['balance'] = $stats['Account']['Ledger']['balance'];
}
// Figure out the outstanding balance of the current lease.
// Figure out the outstanding balance for this customer
$customer['stats'] = $this->stats($id);
// Figure out the total security deposit for the current lease.
$customer['deposits'] = $this->findSecurityDeposits($id);
// Add statistics into the customer account.
$customer['Account'] = array_merge($customer['Account'],
$customer['stats']['Account']['Ledger']);
return $customer;
}
@@ -232,29 +195,11 @@ class Customer extends AppModel {
if (!$id)
return null;
// Get the basic information necessary
$customer = $this->find('first',
array('contain' =>
array('Account' => array
('fields' => array('Account.id')),
'Lease' => array
('fields' => array('Lease.id'))
),
'conditions' => array
(array('Customer.id' => $id))));
// Get stats from the customer account, and each lease
$stats['Account'] = $this->Account->stats($customer['Account']['id']);
foreach ($customer['Lease'] AS $lease) {
$this->statsMerge($stats['Lease'], $this->Lease->stats($lease['id']));
}
// Merge the stats from both the customer specific account, as
// well as the leases. This will provide current customer standing.
$this->statsMerge($stats, $stats['Account']['Ledger']);
$this->statsMerge($stats, $stats['Lease']['Account']['Ledger']);
$stats = $this->Account->stats($this->Account->accountReceivableAccountID(), true,
array('LedgerEntry.customer_id' => $id));
// Pull to the top level and return
$stats = $stats['Ledger'];
return $stats;
}

View File

@@ -25,11 +25,18 @@ class Lease extends AppModel {
var $belongsTo = array(
'LeaseType',
'Unit',
'Account',
'Customer',
'LateSchedule',
);
var $hasMany = array(
'LedgerEntry',
// Cheat to get Account set as part of this class
'Account',
);
/**************************************************************************
**************************************************************************
**************************************************************************
@@ -37,15 +44,7 @@ class Lease extends AppModel {
* - Returns the accountId of the given lease
*/
function accountId($id) {
$this->cacheQueries = true;
$lease = $this->find('first', array
('recursive' => -1,
'fields' => array('account_id'),
'conditions' => array(array('id' => $id)),
));
$this->cacheQueries = false;
return $lease['Lease']['account_id'];
return $this->Account->invoiceAccountID();
}
@@ -61,6 +60,10 @@ class Lease extends AppModel {
/* 'args' => compact('id', 'all', 'cond', 'link'), */
/* )); */
if (!isset($cond))
$cond = array();
$cond[] = array('LedgerEntry.lease_id' => $id);
$entries = $this->Account->findLedgerEntries($this->accountId($id),
$all, $cond, $link);
@@ -87,7 +90,7 @@ class Lease extends AppModel {
$entries = $this->Account->findLedgerEntriesRelatedToAccount
($this->accountId($id),
$this->Account->securityDepositAccountID(),
true, null, $link);
true, array('LedgerEntry.lease_id' => $id), $link);
/* pr(array('function' => 'Lease::findSecurityDeposits', */
/* 'args' => compact('id', 'link'), */
@@ -108,7 +111,7 @@ class Lease extends AppModel {
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
return $this->Account->findUnreconciledLedgerEntries
($this->accountId($id), $fundamental_type);
($this->accountId($id), $fundamental_type, array('LedgerEntry.lease_id' => $id));
}
@@ -128,7 +131,7 @@ class Lease extends AppModel {
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
return $this->Account->reconcileNewLedgerEntry
($this->accountId($id), $fundamental_type, $amount);
($this->accountId($id), $fundamental_type, $amount, array('LedgerEntry.lease_id' => $id));
}
@@ -143,18 +146,11 @@ class Lease extends AppModel {
if (!$id)
return null;
// Find the associated account.
$lease = $this->find('first',
array('recursive' => -1,
'conditions' => array(array('Lease.id' => $id))));
// Pull the stats from the account.
$stats['Account'] = $this->Account->stats($lease['Lease']['account_id']);
// Place a summary of the stats (one lease account in this case)
// at the top level for easy summarized access.
$this->statsMerge($stats, $stats['Account']['Ledger']);
$stats = $this->Account->stats($this->Account->accountReceivableAccountID(), true,
array('LedgerEntry.lease_id' => $id));
// Pull to the top level and return
$stats = $stats['Ledger'];
return $stats;
}

View File

@@ -115,6 +115,9 @@ class Ledger extends AppModel {
* - Returns summary data from the requested ledger.
*/
function stats($id, $cond = null) {
if (!isset($cond))
$cond = array();
$cond[] = array('Ledger.id' => $id);
$stats = $this->find
('first', array
@@ -138,8 +141,7 @@ class Ledger extends AppModel {
) * IF(LedgerEntry.amount, LedgerEntry.amount, 0)
) AS balance",
"COUNT(LedgerEntry.id) AS entries"),
'conditions' => array(isset($cond) ? $cond : array(),
array('Ledger.id' => $id)),
'conditions' => $cond,
'group' => 'Ledger.id',
));

View File

@@ -11,6 +11,8 @@ class LedgerEntry extends AppModel {
var $belongsTo = array(
'MonetarySource',
'Transaction',
'Customer',
'Lease',
'DebitLedger' => array(
'className' => 'Ledger',
@@ -84,7 +86,7 @@ class LedgerEntry extends AppModel {
return $fields;
}
function ledgerContextFields2($ledger_id = null, $account_type = null) {
function ledgerContextFields2($ledger_id = null, $account_id = null, $account_type = null) {
$fields = array('id', 'name', 'comment', 'amount');
if (isset($ledger_id)) {
@@ -93,20 +95,29 @@ class LedgerEntry extends AppModel {
$fields[] = ("IF(LedgerEntry.credit_ledger_id = $ledger_id," .
" SUM(LedgerEntry.amount), NULL) AS credit");
if (isset($account_type)) {
if (in_array($account_type, array('ASSET', 'EXPENSE')))
$ledger_type = 'debit';
else
$ledger_type = 'credit';
$fields[] = ("(IF(LedgerEntry.{$ledger_type}_ledger_id = $ledger_id," .
if (isset($account_id) || isset($account_type)) {
$Account = new Account();
$account_ftype = $Account->fundamentalType($account_id ? $account_id : $account_type);
$fields[] = ("(IF(LedgerEntry.{$account_ftype}_ledger_id = $ledger_id," .
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
}
}
elseif (isset($account_id)) {
$fields[] = ("IF(DebitLedger.account_id = $account_id," .
" SUM(LedgerEntry.amount), NULL) AS debit");
$fields[] = ("IF(CreditLedger.account_id = $account_id," .
" SUM(LedgerEntry.amount), NULL) AS credit");
$Account = new Account();
$account_ftype = ucfirst($Account->fundamentalType($account_id));
$fields[] = ("(IF({$account_ftype}Ledger.account_id = $account_id," .
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
}
return $fields;
}
function ledgerContextConditions($ledger_id, $account_type) {
if (isset($ledger_id)) {
return array

View File

@@ -63,12 +63,14 @@ echo $this->element('leases',
/**********************************************************************
* Account
* Customer Account History
*/
echo $this->element('accounts',
echo $this->element('ledger_entries',
array('caption' => 'Account',
'accounts' => array($customer['Account'])));
'customer_id' => $customer['Customer']['id'],
'ar_account' => true,
));
/* End "detail supporting" div */

View File

@@ -143,8 +143,12 @@ foreach ($jqGridColumns AS &$col) {
}
// Set the default sort column
reset($jqGridColumns);
$sortname = current($jqGridColumns);
if (isset($sort_column)) {
$sortname = $jqGridColumns[$sort_column];
} else {
reset($jqGridColumns);
$sortname = current($jqGridColumns);
}
$sortname = $sortname['index'];
// Configure the grid setup, giving priority to user defined parameters
@@ -185,10 +189,14 @@ $jqGrid_setup = array_merge
jQuery(document).ready(function(){
currencyFormatter = function(el, cellval, opts) {
if (!cellval)
return;
$(el).html(fmtCurrency(cellval));
}
idFormatter = function(el, cellval, opts) {
if (!cellval)
return;
$(el).html('#'+cellval);
}

View File

@@ -1,5 +1,30 @@
<?php /* -*- mode:PHP -*- */
if (isset($account_ftype) || isset($ledger_id) || isset($account_id) || isset($ar_account)) {
$single_account = true;
} else {
$single_account = false;
}
if (isset($ledger_id) || isset($account_id) || isset($ar_account)) {
$single_amount = false;
} else {
$single_amount = true;
}
if (isset($reconcile_id)) {
$applied_amount = true;
} else {
$applied_amount = false;
}
if (isset($account_ftype)) {
$subtotal_amount = false;
} else {
$subtotal_amount = true;
}
// Define the table columns
$cols = array();
if (0) {
@@ -8,33 +33,45 @@ if (isset($notxgroup))
else
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
} else {
$notxgroup = true;
$notxgroup = false;
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
$cols['Entry'] = array('index' => 'LedgerEntry.id', 'formatter' => 'id');
}
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
if (isset($account_ftype) || isset($ledger_id)) {
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'longname');
if ($single_account) {
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name');
}
else {
$cols['Debit Account'] = array('index' => 'DebitAccount.name', 'formatter' => 'longname');
$cols['Credit Account'] = array('index' => 'CreditAccount.name', 'formatter' => 'longname');
$cols['Debit Account'] = array('index' => 'DebitAccount.name', 'formatter' => 'name');
$cols['Credit Account'] = array('index' => 'CreditAccount.name', 'formatter' => 'name');
}
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id');
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'name');
$cols['Source'] = array('index' => 'MonetarySource.name', 'formatter' => 'name');
$cols['Comment'] = array('index' => 'LedgerEntry.comment', 'formatter' => 'comment', 'width'=>150);
if (isset($ledger_id)) {
if ($single_amount) {
$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency');
}
else {
$cols['Debit'] = array('index' => 'debit', 'formatter' => 'currency');
$cols['Credit'] = array('index' => 'credit', 'formatter' => 'currency');
}
else {
$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency');
}
if (isset($reconcile_id)) {
if ($applied_amount) {
$cols['Applied'] = array('index' => "Reconciliation.amount", 'formatter' => 'currency');
}
$custom_post_data = compact('ledger_id', 'account_type', 'account_ftype', 'notxgroup');
if ($subtotal_amount) {
$cols['Sub-Total'] = array('index' => 'subtotal', 'formatter' => 'currency', 'sortable' => false);
}
$custom_post_data = compact('ledger_id', 'account_id', 'ar_account',
'account_type', 'account_ftype',
'customer_id', 'lease_id', 'transaction_id', 'notxgroup');
$jqGrid_options = array('jqGridColumns' => $cols,
'controller' => 'ledger_entries',
@@ -42,11 +79,6 @@ $jqGrid_options = array('jqGridColumns' => $cols,
$jqGrid_options += compact('grid_div_id', 'grid_id', 'caption', 'grid_setup', 'limit');
if (isset($ledger_id)) {
$jqGrid_options += array('action' => 'ledger',
'limit' => 50);
}
if (isset($ledger_entries)) {
$jqGrid_options += array('custom_ids' =>
array_map(create_function('$data',
@@ -54,6 +86,10 @@ if (isset($ledger_entries)) {
$ledger_entries),
'limit' => 10);
}
else {
$jqGrid_options += array('action' => 'ledger',
'limit' => 50);
}
if (isset($reconcile_id)) {
$custom_post_data += compact('reconcile_id');
@@ -61,5 +97,6 @@ if (isset($reconcile_id)) {
}
$jqGrid_options += compact('custom_post_data');
$jqGrid_options['sort_column'] = 'Date';
echo $this->element('jqGrid', $jqGrid_options);

View File

@@ -11,7 +11,6 @@ echo '<div class="lease view">' . "\n";
$lease_type = $lease['LeaseType'];
$customer = $lease['Customer'];
$account = $lease['Account'];
$unit = $lease['Unit'];
if (isset($lease['Lease']))
@@ -20,7 +19,7 @@ if (isset($lease['Lease']))
$rows = array(array('ID', $lease['id']),
array('Number', $lease['number']),
array('Lease Type', $lease_type['name']),
array('Unit', $html->link($unit['id'],
array('Unit', $html->link($unit['name'],
array('controller' => 'units',
'action' => 'view',
$unit['id']))),
@@ -36,10 +35,6 @@ $rows = array(array('ID', $lease['id']),
array('Notice Given', FormatHelper::date($lease['notice_given_date'], true)),
array('Notice Received', FormatHelper::date($lease['notice_received_date'], true)),
array('Closed', FormatHelper::date($lease['close_date'], true)),
array('Account', $html->link($account['name'],
array('controller' => 'accounts',
'action' => 'view',
$account['id']))),
array('Deposit', FormatHelper::currency($lease['deposit'])),
array('Rent', FormatHelper::currency($lease['amount'])),
array('Comment', $lease['comment']));
@@ -53,7 +48,7 @@ echo $this->element('table',
/**********************************************************************
* Account Info Box
* Lease Info Box
*/
echo '<div class="infobox">' . "\n";
@@ -80,15 +75,16 @@ echo '<div CLASS="detail supporting">' . "\n";
/**********************************************************************
* Current Ledger
* Lease Account History
*/
echo $this->element('ledger_entries',
array('caption' => "Current Ledger: (#{$account['id']}-{$account['CurrentLedger']['sequence']})",
'ledger_id' => $account['CurrentLedger']['id'],
'account_type' => $account['type'],
array('caption' => 'Account',
'lease_id' => $lease['id'],
'ar_account' => true,
));
/* End "detail supporting" div */
echo '</div>' . "\n";

View File

@@ -13,6 +13,8 @@ $transaction = $entry['Transaction'];
$debit_ledger = $entry['DebitLedger'];
$credit_ledger = $entry['CreditLedger'];
$source = $entry['MonetarySource'];
$customer = $entry['Customer'];
$lease = $entry['Lease'];
$entry = $entry['LedgerEntry'];
$rows = array(array('ID', $entry['id']),
@@ -21,8 +23,20 @@ $rows = array(array('ID', $entry['id']),
'action' => 'view',
$transaction['id']))),
array('Timestamp', FormatHelper::datetime($transaction['stamp'])),
array('Monetary Source', (isset($source['id'])
? $html->link('#'.$source['id'],
array('Customer', (isset($customer['name'])
? $html->link($customer['name'],
array('controller' => 'customers',
'action' => 'view',
$customer['id']))
: null)),
array('Lease', (isset($lease['id'])
? $html->link('#'.$lease['id'],
array('controller' => 'leases',
'action' => 'view',
$lease['id']))
: null)),
array('Monetary Source', (isset($source['name'])
? $html->link($source['name'],
array('controller' => 'monetary_sources',
'action' => 'view',
$source['id']))
@@ -67,12 +81,16 @@ echo $this->element('table',
echo '<div class="infobox">' . "\n";
$rows = array();
if ($debit_ledger['Account']['trackable']) {
$rows[] = array('Debit Amount Reconciled:', FormatHelper::currency($stats['debit_amount_reconciled']));
$rows[] = array('Debit Amount Remaining:', FormatHelper::currency($stats['debit_amount_remaining']));
$rows[] = array("Applied from {$debit_ledger['Account']['name']}:",
FormatHelper::currency($stats['debit_amount_reconciled']));
$rows[] = array("{$debit_ledger['Account']['name']} Amount Remaining:",
FormatHelper::currency($stats['debit_amount_remaining']));
}
if ($credit_ledger['Account']['trackable']) {
$rows[] = array('Credit Amount Reconciled:', FormatHelper::currency($stats['credit_amount_reconciled']));
$rows[] = array('Credit Amount Remaining:', FormatHelper::currency($stats['credit_amount_remaining']));
$rows[] = array("Applied to {$credit_ledger['Account']['name']}:",
FormatHelper::currency($stats['credit_amount_reconciled']));
$rows[] = array("{$credit_ledger['Account']['name']} Amount Remaining:",
FormatHelper::currency($stats['credit_amount_remaining']));
}
echo $this->element('table',
array('class' => 'summary',
@@ -97,26 +115,25 @@ echo '<div CLASS="detail supporting">' . "\n";
* Reconciliation Ledger Entries
*/
echo $this->element('ledger_entries',
array('caption' => "Debit Applications",
'grid_div_id' => 'debit_reconciliation_ledger_entries',
//'ledger_id' => $debit_ledger['id'],
//'account_type' => $debit_ledger['Account']['type'],
'account_ftype' => 'debit',
'reconcile_id' => $entry['id'],
'ledger_entries' => $reconciled['debit']['entry'],
));
if ($debit_ledger['Account']['trackable']) {
echo $this->element('ledger_entries',
array('caption' => "Applied to " . $debit_ledger['Account']['name'],
'grid_div_id' => 'debit_reconciliation_ledger_entries',
'account_ftype' => 'debit',
'reconcile_id' => $entry['id'],
//'ledger_entries' => $reconciled['debit']['entry'],
));
}
echo $this->element('ledger_entries',
array('caption' => "Credit Applications",
'grid_div_id' => 'credit_reconciliation_ledger_entries',
//'ledger_id' => $credit_ledger['id'],
//'account_type' => $credit_ledger['Account']['type'],
//'account_ftype' => 'debit', // Looking for debits to match this credit
'account_ftype' => 'credit',
'reconcile_id' => $entry['id'],
'ledger_entries' => $reconciled['credit']['entry'],
));
if ($credit_ledger['Account']['trackable']) {
echo $this->element('ledger_entries',
array('caption' => "Applied to " . $credit_ledger['Account']['name'],
'grid_div_id' => 'credit_reconciliation_ledger_entries',
'account_ftype' => 'credit',
'reconcile_id' => $entry['id'],
//'ledger_entries' => $reconciled['credit']['entry'],
));
}
/* End "detail supporting" div */

View File

@@ -54,7 +54,8 @@ echo '<div CLASS="detail supporting">' . "\n";
echo $this->element('ledger_entries',
array('caption' => 'Entries in Transaction',
'ledger_entries' => $transaction['LedgerEntry'],
//'ledger_entries' => $transaction['LedgerEntry'],
'transaction_id' => $transaction['Transaction']['id'],
'notxgroup' => true,
));

View File

@@ -57,16 +57,16 @@ echo $this->element('leases',
/**********************************************************************
* Ledger History
* Current Tenant Lease Account History
*/
/* foreach($unit['Lease'] AS $lease) { */
/* pr($lease); */
/* $caption = 'Lease #'.$lease['number'].' (Tenant: '.$lease['Customer']['name'].')'; */
/* echo $this->element('lease', */
/* array('caption' => $caption, */
/* 'entries' => $lease['Customer']['Transaction'], */
/* 'ledger' => array('mix'=>1))); */
/* } */
echo $this->element('ledger_entries',
array('caption' => ('Current Lease Account (' .
$unit['CurrentLease']['Customer']['name']
. ')'),
'ar_account' => true,
'lease_id' => $unit['CurrentLease']['id'],
));
/* End "detail supporting" div */

View File

@@ -65,8 +65,10 @@ tr.evnrow { background: #f4f4f4; }
*/
table.detail { width : 60%;
float: left; }
table.detail td.field { width: 10em; }
float : left; }
table.detail td.field { width : 10em; }
table.item.detail td.value { white-space : normal; }
div.detail.supporting { clear : both;
padding-top: 1.5em; }