Compare commits

...

34 Commits

Author SHA1 Message Date
abijah
011d1208e8 Initial thoughts on the charge reversal. It's obviously not working yet, or I wouldn't leave it hanging on a branch.
git-svn-id: file:///svn-source/pmgr/branches/alt_reversal_20090806@502 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-07 04:20:20 +00:00
abijah
9dd09c4d2e Branch created to save some changes/thoughts on charge reversals. As of branch creation, we're just deleting the current disbursements on the charge, and deletion is never good, since we lose information by definition. For example, the collected rents report will be adversely affected. So, we need to figure out how to keep the disbursement, and yet still have everything work out correctly. That work will be started on this branch.
git-svn-id: file:///svn-source/pmgr/branches/alt_reversal_20090806@501 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-07 04:18:13 +00:00
abijah
2e36d46329 Minor tweaks, a shame for checkin r500 :-(
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@500 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-07 03:55:01 +00:00
abijah
1dd0b14861 More work with security deposits, reversals, and balances. I've tried to work many different corner cases, but know that not everything has been tested. I think the next steps for testing will be to put in some real data.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@499 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-07 00:10:28 +00:00
abijah
d75cd10f49 Added in internal error function, since the die() statements were hard to spot, and certainly not user friendly for the end user.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@498 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 20:17:02 +00:00
abijah
a69a56c715 Fixed the button text for new customers
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@497 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 18:08:55 +00:00
abijah
8f7cf202e5 Fixed the customer selection update for receipts, and added a mechanism to automatically update the oustanding charges grid after entering the receipt.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@496 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 18:08:21 +00:00
abijah
58c4f28956 Added mechanism to do a full replacement of specified post parameters, instead of just merging.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@495 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 18:06:57 +00:00
abijah
f3eaa40ea5 Added ability to edit a unit, except for sort/walk order. To handle those things we'll need to: save unit's old sort/walk position; adjust down (by one) all unit positions greater than the old position; adjust up (by one) all unit positions greater than or equal to the new unit position; update the unit's position. I'm not going to worry about it right now.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@494 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 09:27:09 +00:00
abijah
e784931fa8 Implemented refund, at least for the most part. Minor testing, but looks promising. Because of this change the customer account entries grid appears odd, with a refunds showing up as a 'Charge'. So, I'm toying with the idea of having entries show up as customer 'Debits' and 'Credits'. I don't know if this will cause user confusion, but we'll play with it for a while and see. It actually reminds me a bit (coming full circle) of the earliest implementations, which kept track of a lease on its own account/ledger, in which credit/debit would be the exact correct terms.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@493 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 05:11:58 +00:00
abijah
5a7b087ddc Added ability to format currency without a dollar sign (i.e. in raw numerical format).
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@492 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 05:04:34 +00:00
abijah
4d62d7da73 More work on refund. I'm going to skip the whole voucher/credit_note bit and simply present a page that lets the user enter a date, an account, and the amount to refund, recording 1 payment transaction.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@491 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-06 02:52:45 +00:00
abijah
cca698d437 Several changes in an effort to support charge reversals. I can't imagine this is all working flawlessly, as I'm not quite sure how it even _should_ work.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@490 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-05 07:54:57 +00:00
abijah
5247bb8db6 Modified to use toggle for 'this' debug pr block, instead of independent show/hide
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@489 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-05 02:59:09 +00:00
abijah
cb969ba340 Discovered that the use of the term 'Payment' has been a misnomer. A company uses the term payment to indicate the monies it pays to _others_. Changing this leaves us without a good replacement term, but disbursement really seems to fit the bill. As comfortable as the term payment was, it was odd in many respects and disbursement does seem more appropriate. I'm sure there are several lingering bugs from this massive search/replace exercise, but this is a decent baseline for the change.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@488 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-05 01:00:09 +00:00
abijah
094e15ddf9 Added a more descriptive fieldname for statement entry views. Instead of Transaction, it will be Receipt, Invoice, Deposit, etc.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@487 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-05 00:06:46 +00:00
abijah
670f0894ea Brought the bad debt write-off functionality up to date. I'm not entirely convinced that calling it a receipt is such a good idea, but bad debt is certainly a non-normal case, and we can work on this later if need be.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@486 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 23:25:31 +00:00
abijah
9e20473b3b Modified lease and customer views to have the statement & ledger entries sorted in descending date order by default. May not prefer this in the end, but we'll give it a go for a while.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@485 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 22:12:32 +00:00
abijah
11d5deac85 Undid the change from r401, since it wasn't being used and was interferring with the specification of sort_column. If we really want that functionality, we can add it in when needed and come up with a more general solution for view specific sorting as well.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@484 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 22:09:18 +00:00
abijah
67280c89e7 Fixed bug when marking NSF of a tender that has only been used as a surplus and never applied to any charges. Modified the applyCredits algorithm to try and distinguish between surplus credits of a lease and general customer surplus. I don't know if it works completely, but I do know it creates an issue since a lease surplus can now never be utilized without additional lease charges, a refund (no yet implemented), or moving the surplus to the customer level (not intended to be implemented).
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@483 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 21:19:20 +00:00
abijah
1afed6a6e0 Made the link clearer that we'll only be waiving the balance of a charge, not the entire charge.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@482 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 20:26:33 +00:00
abijah
0ff91bf4d8 Even with all the effort put into getting the counts right on the customers grid, it still didn't work right. The root of the problem is the join to CurrentLease, which can result in multiple rows for the same customer. I can revisit this in the future to put some clever solution back in, but in the meantime, it was easiest just to add fields to the customers table, and simply update it whenever the customer lease situation changes. I don't like it... but it just made life much simpler.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@481 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 19:25:39 +00:00
abijah
4d4e96fe1c More work cleaning up and testing the use of charge waivers, as well as security deposit utilizations. Much of this was just around the concept of determining balances, which wasn't / isn't very straightforward. It's not hard to calculate a balance for a particular sitation, but it is difficult to generalize. I think it's reasonable as it now stands, but the StatementEntry::stats algorithm is certainly subject to change. Also, I didn't double check every case which called the stats() function, so I highly suspect we'll find cases where the code is not expecting the new return values, either symantically or logically.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@480 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-04 03:12:09 +00:00
abijah
32a98b4b6e Continued waiver progress. At the moment, it works ok, but I don't like the way that security deposit balances work. It's probably a general issue, not just security deposits, but it's not clear whether stats from StatementEntry should be subtracting waiver totals from the overall charge reconciliation total. It should in some cases, and not in others. I'll tweak on it in later checkins.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@479 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 21:50:21 +00:00
abijah
817b74b085 Fixed minor bug that was causing the string 'undefined' to be used as the payment index.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@478 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 21:47:24 +00:00
abijah
dc1bb56188 Fixed cut/paste error causing invalid customer security deposit balance.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@477 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 21:44:36 +00:00
abijah
4707f3314d Added HR to keep the boundary between two pr blocks clear even when the output is collapsed and the stack trace is not.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@476 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 20:17:14 +00:00
abijah
ac2b1530fc Added ability to log items without showing the output. I doubt the mechanism works perfectly in all cases, but it works enough for now and I can tweak it as needed.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@475 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 20:13:48 +00:00
abijah
a5d3ff0b70 Added the ability to suppress logging at display time. I'd like to add an additional log level, the first which passes or suppresses the print, and the second which defaultly displays or hides the output. Perhaps next checkin.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@474 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 19:41:49 +00:00
abijah
adc87c0763 Added stack tracing to each model::pr print
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@473 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 18:38:35 +00:00
abijah
7d81b9766b Added ability to waive a partial charge.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@472 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 04:14:44 +00:00
abijah
1aa6273ade First pass at a charge waiver implementation. It hasn't been tested but for a tiny bit.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@471 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 03:46:58 +00:00
abijah
2c08405d5a Work around for the fact that our implementation has issues with receipts of more than one tender. See the branch statement_ledger_entry_tie_20090802 for more information.
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@470 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 03:45:51 +00:00
abijah
e2ed6ed1c7 Left r466 with syntax error... fixed that
git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@469 97e9348a-65ac-dc4b-aefc-98561f571b83
2009-08-03 02:30:29 +00:00
41 changed files with 1654 additions and 1101 deletions

View File

@@ -644,6 +644,17 @@ CREATE TABLE `pmgr_customers` (
-- contacts_customers table?
`primary_contact_id` INT(10) UNSIGNED NOT NULL,
-- Number of different leases for this customer.
-- It's not good to have redundant information,
-- but these fields change infrequently, and make
-- certain queries much easier, most notably for
-- the grid query, in which linking customer to
-- lease results in repeated statement entries
-- when a customer has more than one lease.
`lease_count` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`current_lease_count` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`past_lease_count` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`comment` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
@@ -844,8 +855,8 @@ CREATE TABLE `pmgr_accounts` (
-- normal circumstances, when a debit occurs.
-- `trackable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
`deposits` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for deposits?
`charges` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for charges?
`payments` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for payments?
`invoices` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for invoices?
`receipts` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for receipts?
`refunds` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for refunds?
-- Security Level
@@ -873,20 +884,21 @@ INSERT INTO `pmgr_accounts` (`type`, `name`)
-- us identify how serious the NSF situation is.
('ASSET', 'NSF' ),
('LIABILITY', 'A/P' );
INSERT INTO `pmgr_accounts` (`type`, `name`, `payments`, `refunds`)
INSERT INTO `pmgr_accounts` (`type`, `name`, `receipts`, `refunds`)
VALUES
('ASSET', 'Cash', 1, 1),
('ASSET', 'Cash', 1, 0),
('ASSET', 'Check', 1, 0),
('ASSET', 'Money Order', 1, 0),
('ASSET', 'ACH', 1, 0),
('ASSET', 'Closing', 0, 0), -- REVISIT <AP>: Temporary
('EXPENSE', 'Concession', 1, 0);
('EXPENSE', 'Concession', 1, 0),
('EXPENSE', 'Waiver', 0, 0);
INSERT INTO `pmgr_accounts` (`type`, `name`, `refunds`, `deposits`)
VALUES
-- REVISIT <AP>: 20090710 : We probably don't really want petty cash depositable.
-- This is just for testing our deposit code
('ASSET', 'Petty Cash', 1, 1);
INSERT INTO `pmgr_accounts` (`type`, `name`, `charges`)
INSERT INTO `pmgr_accounts` (`type`, `name`, `invoices`)
VALUES
('LIABILITY', 'Tax', 1),
('LIABILITY', 'Security Deposit', 1),
@@ -949,14 +961,19 @@ DROP TABLE IF EXISTS `pmgr_transactions`;
CREATE TABLE `pmgr_transactions` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` ENUM('INVOICE',
'RECEIPT',
-- REVISIT <AP>: 20090804
-- I'm not sure about most of these terms.
-- Just as long as they're distinct though... I can rename them later
`type` ENUM('INVOICE', -- Sales Invoice
'RECEIPT', -- Actual receipt of monies
'PURCHASE_INVOICE', -- Committment to pay
'CREDIT_NOTE', -- Inverse of Sales Invoice
'PAYMENT', -- Actual payment
'DEPOSIT',
'CLOSE',
'CLOSE', -- Essentially an internal (not accounting) transaction
-- 'CREDIT',
-- 'REFUND',
-- 'WAIVER',
'TRANSFER')
'TRANSFER') -- Unsure of TRANSFERs use
NOT NULL,
`stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -981,10 +998,6 @@ CREATE TABLE `pmgr_transactions` (
-- (e.g. A/R, Bank, etc)
`account_id` INT(10) UNSIGNED DEFAULT NULL,
`ledger_id` INT(10) UNSIGNED DEFAULT NULL,
-- For convenience. Actually, INVOICE will always set crdr
-- to DEBIT, RECEIPT will use CREDIT, and DEPOSIT will use
-- DEBIT
`crdr` ENUM('DEBIT',
'CREDIT')
DEFAULT NULL,
@@ -1013,9 +1026,6 @@ CREATE TABLE `pmgr_ledger_entries` (
-- The account/ledger of the entry
`account_id` INT(10) UNSIGNED NOT NULL,
`ledger_id` INT(10) UNSIGNED NOT NULL,
-- For convenience. Actually, CHARGE will always set crdr
-- to CREDIT and PAYMENT will use DEBIT.
`crdr` ENUM('DEBIT',
'CREDIT')
NOT NULL,
@@ -1061,19 +1071,26 @@ DROP TABLE IF EXISTS `pmgr_statement_entries`;
CREATE TABLE `pmgr_statement_entries` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` ENUM('CHARGE',
'PAYMENT',
'SURPLUS',
-- REVISIT <AP>: 20090804
-- I'm not sure about most of these terms.
-- Just as long as they're distinct though... I can rename them later
`type` ENUM('CHARGE', -- Invoiced Charge to Customer
'DISBURSEMENT', -- Disbursement of Receipt Funds
'REVERSAL', -- Reversal of a charge
'VOUCHER', -- Agreement to pay
'PAYMENT', -- Payment of a Voucher
'REFUND', -- Payment due to refund
'SURPLUS', -- Surplus Receipt Funds
'WAIVER', -- Waived Charge
-- REVISIT <AP>: 20090730
-- VOID is just to test out a theory
-- for handling NSF and charge reversals
'WAIVE',
-- VOID is used for handling NSF and perhaps charge reversals.
-- It's not clear this is the best way to handle these things.
'VOID')
NOT NULL,
`transaction_id` INT(10) UNSIGNED NOT NULL,
-- Effective date is when the charge/payment/transfer actually
-- Effective date is when the charge/disbursement/transfer actually
-- takes effect (since it may not be at the time of the transaction).
-- Through date is used if/when a charge covers a certain time period,
-- like rent. A security deposit, for example, would not use the
@@ -1098,7 +1115,7 @@ CREATE TABLE `pmgr_statement_entries` (
-- in the statement. Keeping it for now...
`account_id` INT(10) UNSIGNED DEFAULT NULL,
-- Allow the payment to reconcile against the charge
-- Allow the disbursement to reconcile against the charge
`charge_entry_id` INT(10) UNSIGNED DEFAULT NULL,
`comment` VARCHAR(255) DEFAULT NULL,

View File

@@ -50,19 +50,19 @@ Operations to be functional
X - Assess NSF Fees
X - Determine Lease Paid-Through status
- Report: List of customers overdue
- Flag unit as overlocked
- Flag unit as evicting
- Flag unit as normal status
- Flag unit as dirty
X - Flag unit as overlocked
X - Flag unit as evicting
X - Flag unit as normal status
X - Flag unit as dirty
- Enter notes when communicating with Customer
X - Accept pre-payments
X - Record Customer Move-Out from Unit
? - Record utilization of Security Deposit
- Record issuing of a refund
X - Record utilization of Security Deposit
X - Record issuing of a refund
- Record Deposit into Petty Cash
- Record Payment from Petty Cash to expenses
- Record Petty Cash to refund.
? - Write Off Bad Debt
X - Record Petty Cash to refund.
X - Write Off Bad Debt
X - Perform a Deposit
X - Close the Books (nightly / weekly, etc)
X - Determine Rents Collected for a given period.

View File

@@ -1308,18 +1308,13 @@ foreach $row (@{query($sdbh, $query)})
# 'effective_date' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'effective_date'},
# 'effective_date' => $effective_date;
# 'through_date' => $through_date;
'lease_id' => ($row->{'entry_type'} eq 'CREDIT'
? 0
: $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}),
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'amount' => $reconcile_amount,
'account_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_account_id'},
};
$row->{'ChargeID'} = undef
if $row->{'entry_type'} eq 'CREDIT';
# Update the receipt & tender customer_id, now that we have payment info
$newdb{'tables'}{'transactions'}{'rows'}[
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'receipt_id'}
@@ -1336,7 +1331,7 @@ foreach $row (@{query($sdbh, $query)})
# Add the Payment Statement Entry
addRow('statement_entries', {
'transaction_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'receipt_id'},
'type' => $row->{'entry_type'} || 'PAYMENT',
'type' => 'DISBURSEMENT',
# 'effective_date' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'effective_date'},
# 'through_date' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'through_date'},
'effective_date' => $effective_date,
@@ -1471,6 +1466,25 @@ addRow('double_entries', {
});
######################################################################
## Debug ... work from scratch
if (defined $work_from_scratch) {
# delete $newdb{'tables'}{'contacts'}{'rows'};
# delete $newdb{'tables'}{'contacts_methods'}{'rows'};
# delete $newdb{'tables'}{'contacts_addresses'}{'rows'};
# delete $newdb{'tables'}{'contacts_emails'}{'rows'};
# delete $newdb{'tables'}{'contacts_phones'}{'rows'};
delete $newdb{'tables'}{'contacts_customers'}{'rows'};
delete $newdb{'tables'}{'customers'}{'rows'};
delete $newdb{'tables'}{'double_entries'}{'rows'};
delete $newdb{'tables'}{'leases'}{'rows'};
delete $newdb{'tables'}{'ledger_entries'}{'rows'};
delete $newdb{'tables'}{'statement_entries'}{'rows'};
delete $newdb{'tables'}{'tenders'}{'rows'};
delete $newdb{'tables'}{'transactions'}{'rows'};
}
######################################################################
## Build the Database
@@ -1530,6 +1544,21 @@ $query = "UPDATE pmgr_units U, pmgr_leases L
WHERE L.unit_id = U.id AND L.close_date IS NULL";
query($db_handle, $query);
# All current_lease_counts will be zero at the moment, since
# everything was just created. This will update any customers
# that have ever leased anything.
$query = "UPDATE pmgr_customers C,
(SELECT L.customer_id,
SUM(IF(L.close_date IS NULL, 1, 0)) AS current,
SUM(IF(L.close_date IS NULL, 0, 1)) AS closed
FROM pmgr_leases L
GROUP BY L.customer_id) AS X
SET C.`lease_count` = X.current + X.closed,
C.`current_lease_count` = X.current,
C.`past_lease_count` = X.closed
WHERE X.customer_id = C.id";
query($db_handle, $query);
######################################################################
## Invoice/Receipt totals

View File

@@ -47,6 +47,8 @@ class AppController extends Controller {
array('name' => 'Customers', 'url' => array('controller' => 'customers', 'action' => 'index')),
array('name' => 'Accounts', 'url' => array('controller' => 'accounts', 'action' => 'index')),
array('name' => 'Debug', 'header' => true),
array('name' => 'Un-Nuke', 'url' => '#', 'htmlAttributes' =>
array('onclick' => '$(".pr-section").show(); return false;')),
array('name' => 'Contacts', 'url' => array('controller' => 'contacts', 'action' => 'index')),
array('name' => 'Ledgers', 'url' => array('controller' => 'ledgers', 'action' => 'index')),
array('name' => 'New Ledgers', 'url' => array('controller' => 'accounts', 'action' => 'newledger')),
@@ -187,17 +189,26 @@ class AppController extends Controller {
// This SHOULD always be set, except when debugging
if (isset($params['post']))
$params['post'] = unserialize($params['post']);
else
$params['post'] = array();
// Unserialize our complex structure of dynamic post data
if (isset($params['dynamic_post']))
$params['dynamic_post'] = unserialize($params['dynamic_post']);
else
$params['dynamic_post'] = null;
// Unserialize our complex structure of dynamic post data
if (isset($params['dynamic_post_replace']))
$params['dynamic_post_replace'] = unserialize($params['dynamic_post_replace']);
else
$params['dynamic_post_replace'] = null;
// Merge the static and dynamic post data
if (empty($params['post']) && !empty($params['dynamic_post']))
$params['post'] = $params['dynamic_post'];
elseif (!empty($params['post']) && !empty($params['dynamic_post']))
//$params['post'] = array_merge($params['post'], $params['dynamic_post']);
if (!empty($params['dynamic_post']))
$params['post'] = array_merge_recursive($params['post'], $params['dynamic_post']);
if (!empty($params['dynamic_post_replace']))
$params['post'] = array_merge($params['post'], $params['dynamic_post_replace']);
// This SHOULD always be set, except when debugging
if (!isset($params['post']['fields']))
@@ -680,7 +691,7 @@ class AppController extends Controller {
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
// Don't create any links if ordered not to.
if (isset($params['nolinks']))
if (isset($params['post']['nolinks']))
return;
foreach ($links AS $table => $fields) {

View File

@@ -114,20 +114,48 @@ class AppModel extends Model {
$line = $caller['line'];
// So, this caller holds the calling function name
$caller = array_shift($trace);
$caller = $trace[0];
$function = $caller['function'];
$class = $caller['class'];
//$class = $this->name;
// Adjust the log level from default, if necessary
// Use class or function specific log level if available
if (isset($this->class_log_level[$class]))
$log_level = $this->class_log_level[$class];
if (isset($this->function_log_level["{$class}-{$function}"]))
$log_level = $this->function_log_level["{$class}-{$function}"];
if (isset($this->min_log_level))
$log_level = max($log_level, $this->min_log_level);
if (isset($this->max_log_level))
$log_level = min($log_level, $this->max_log_level);
// Establish log level minimums
$min_log_level = $this->min_log_level;
if (is_array($this->min_log_level)) {
$min_show_level = $min_log_level['show'];
$min_log_level = $min_log_level['log'];
}
// Establish log level maximums
$max_log_level = $this->max_log_level;
if (is_array($this->max_log_level)) {
$max_show_level = $max_log_level['show'];
$max_log_level = $max_log_level['log'];
}
// Determine the applicable log and show levels
if (is_array($log_level)) {
$show_level = $log_level['show'];
$log_level = $log_level['log'];
}
// Adjust log level up/down to min/max
if (isset($min_log_level))
$log_level = max($log_level, $min_log_level);
if (isset($max_log_level))
$log_level = min($log_level, $max_log_level);
// Adjust show level up/down to min/max
if (isset($min_show_level))
$show_level = max($show_level, $min_show_level);
if (isset($max_show_level))
$show_level = min($show_level, $max_show_level);
// If the level is insufficient, bail out
if ($level > $log_level)
@@ -141,13 +169,87 @@ class AppModel extends Model {
$mixed = $chk + array($mixed);
}
echo '<DIV CLASS="pr-caller">';
echo '<strong>' . substr(str_replace(ROOT, '', $file), 1) . '</strong>';
echo ' (line <strong>' . $line . '</strong>)';
echo ' : level-' . $level;
echo '</DIV>' . "\n";
static $pr_unique_number = 0;
$pr_id = 'pr-section-class-' . $class . '-print-' . (++$pr_unique_number);
$pr_trace_id = $pr_id . '-trace';
$pr_output_id = $pr_id . '-output';
pr(array("{$class}::{$function}()" => $mixed), false, false);
$pr_entire_base_class = "pr-section";
$pr_entire_class_class = $pr_entire_base_class . '-class-' . $class;
$pr_entire_function_class = $pr_entire_class_class . '-function-' . $function;
$pr_entire_class = "$pr_entire_base_class $pr_entire_class_class $pr_entire_function_class";
$pr_header_class = "pr-caller";
$pr_trace_class = "pr-trace";
$pr_output_base_class = 'pr-output';
$pr_output_class_class = $pr_output_base_class . '-class-' . $class;
$pr_output_function_class = $pr_output_class_class . '-function-' . $function;
$pr_output_class = "$pr_output_base_class $pr_output_class_class $pr_output_function_class";
echo '<DIV class="'.$pr_entire_class.'" id="'.$pr_id.'">'."\n";
echo '<DIV class="'.$pr_header_class.'">'."\n";
echo '<DIV class="'.$pr_trace_class.'" id="'.$pr_trace_id.'" style="display:none;">'."\n";
echo '<HR />' . "\n";
// Flip trace around so the sequence flows from top to bottom
// Then print out the entire stack trace (in hidden div)
$trace = array_reverse($trace);
for ($i = 0; $i < count($trace); ++$i) {
$bline = $trace[$i]['line'];
$bfile = $trace[$i]['file'];
$bfile = str_replace(ROOT.DS, '', $bfile);
$bfile = str_replace(CAKE_CORE_INCLUDE_PATH.DS, '', $bfile);
if ($i > 0) {
$bfunc = $trace[$i-1]['function'];
$bclas = $trace[$i-1]['class'];
} else {
$bfunc = null;
$bclas = null;
}
echo("$bfile:$bline (" . ($bclas ? "$bclas::$bfunc" : "entry point") . ")<BR>\n");
//echo(($bclas ? "$bclas::$bfunc" : "entry point") . "; $bfile : $bline<BR>\n");
}
echo '</DIV>' . "\n"; // End pr_trace_class
$file = str_replace(ROOT.DS, '', $file);
$file = str_replace(CAKE_CORE_INCLUDE_PATH.DS, '', $file);
echo "<strong>$file:$line ($class::$function)</strong>" . ";\n";
/* $log_show_level = isset($show_level) ? $show_level : '?'; */
/* echo ' L' . $level . "({$log_level}/{$log_show_level})" . ";\n"; */
echo ' L' . $level . ";\n";
echo ' <A HREF="#" onclick="$' . "('#{$pr_trace_id}').slideToggle(); return false;" . '">stack</A>'.";\n";
echo " this ";
echo '<A HREF="#" onclick="$' . "('#{$pr_output_id}').slideToggle(); return false;" . '">t</A>'."/";
echo '<A HREF="#" onclick="$' . "('#{$pr_id}').hide(); return false;" . '">n</A>'.";\n";
echo " $class ";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_class_class}').slideDown(); return false;" . '">s</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_class_class}').slideUp(); return false;" . '">h</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_entire_class_class}').hide(); return false;" . '">n</A>'.";\n";
echo " $function ";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_function_class}').slideDown(); return false;" . '">s</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_function_class}').slideUp(); return false;" . '">h</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_entire_function_class}').hide(); return false;" . '">n</A>'.";\n";
echo " all ";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_base_class}').show(); return false;" . '">s</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_output_base_class}').hide(); return false;" . '">h</A>'."/";
echo '<A HREF="#" onclick="$' . "('.{$pr_entire_base_class}').hide(); return false;" . '">n</A>'."\n";
echo '</DIV>' . "\n"; // End pr_header_class
if (isset($show_level) && $level > $show_level)
$display = 'none';
else
$display = 'block';
echo '<DIV class="'.$pr_output_class.'" id="'.$pr_output_id.'" style="display:'.$display.';">'."\n";
pr($mixed, false, false);
echo '</DIV>' . "\n"; // End pr_output_class
echo '</DIV>' . "\n"; // End pr_entire_class
}
function pr($level, $mixed, $checkpoint = null) {

View File

@@ -31,6 +31,39 @@
* You can also use this to include or require any files in your application.
*
*/
function INTERNAL_ERROR($message) {
echo '<DIV class="internal-error" style="color:#000; background:#c22; padding:0.5em 1.5em 0.5em 1.5em;">';
echo '<H1 style="margin-bottom:0.2em">INTERNAL ERROR:</H1>';
echo '<H2 style="margin-top:0; margin-left:1.5em">' . $message . '</H2>';
echo '<H4>This error was not caused by anything that you did wrong.';
echo '<BR>It is a problem within the application itself and should be reported to the administrator.</H4>';
// Print out the entire stack trace
echo "<HR>Stack Trace:<OL>";
$trace = debug_backtrace(false);
for ($i = 0; $i < count($trace); ++$i) {
$bline = $trace[$i]['line'];
$bfile = $trace[$i]['file'];
$bfile = str_replace(ROOT.DS, '', $bfile);
$bfile = str_replace(CAKE_CORE_INCLUDE_PATH.DS, '', $bfile);
if ($i < count($trace)-1) {
$bfunc = $trace[$i+1]['function'];
$bclas = $trace[$i+1]['class'];
} else {
$bfunc = null;
$bclas = null;
}
echo("<LI>$bfile:$bline (" . ($bclas ? "$bclas::$bfunc" : "entry point") . ")</LI>\n");
}
echo '</OL>';
echo '</DIV>';
die();
}
/**
* The settings below can be used to set additional paths to models, views and controllers.
* This is related to Ticket #470 (https://trac.cakephp.org/ticket/470)

View File

@@ -138,17 +138,18 @@ class AccountsController extends AppController {
$this->redirect(array('action'=>'index'));
}
$payment_accounts = $this->Account->collectableAccounts();
//$payment_accounts[$this->Account->nameToID('Closing')] = 'Closing';
//$payment_accounts[$this->Account->nameToID('Equity')] = 'Equity';
$default_accounts = array_diff_key($this->Account->paymentAccounts(),
array($this->Account->concessionAccountID() => 1));
$this->set(compact('payment_accounts', 'default_accounts'));
$this->Account->recursive = -1;
$account = $this->Account->read(null, $id);
$account = $account['Account'];
$payment_accounts = $this->Account->collectableAccounts();
//$payment_accounts[$this->Account->nameToID('Closing')] = 'Closing';
//$payment_accounts[$this->Account->nameToID('Equity')] = 'Equity';
//$payment_accounts[$id] = 'Reversals';
$default_accounts = array_diff_key($this->Account->receiptAccounts(),
array($this->Account->concessionAccountID() => 1));
$this->set(compact('payment_accounts', 'default_accounts'));
$title = ($account['name'] . ': Collected Report');
$this->set(compact('account', 'title'));
}

View File

@@ -50,7 +50,6 @@ class CustomersController extends AppController {
('link' =>
array(// Models
'PrimaryContact',
'CurrentLease' => array('fields' => array()),
),
);
}
@@ -64,19 +63,19 @@ class CustomersController extends AppController {
function gridDataFields(&$params, &$model) {
$fields = parent::gridDataFields($params, $model);
$fields[] = ('COUNT(DISTINCT CurrentLease.id) AS lease_count');
return array_merge($fields,
$this->Customer->StatementEntry->chargePaymentFields(true));
$this->Customer->StatementEntry->chargeDisbursementFields(true));
}
function gridDataConditions(&$params, &$model) {
$conditions = parent::gridDataConditions($params, $model);
if ($params['action'] === 'current') {
$conditions[] = 'CurrentLease.id IS NOT NULL';
$conditions[] = array('Customer.current_lease_count >' => 0);
}
elseif ($params['action'] === 'past') {
$conditions[] = 'CurrentLease.id IS NULL';
$conditions[] = array('Customer.current_lease_count' => 0);
$conditions[] = array('Customer.past_lease_count >' => 0);
}
return $conditions;
@@ -99,30 +98,6 @@ class CustomersController extends AppController {
return $order;
}
function gridDataCount(&$params, &$model) {
if ($params['action'] != 'current')
return parent::gridDataCount($params, $model);
// OK, for current customers, we have an issue.
// We don't have a good way to use the query to obtain
// our count. The problem is that we're relying on the
// group by for the query, but that simply won't work
// for the count. However, it's not difficult to simply
// derive it since 'current' customers are mutually
// exclusive to 'past' customers.
$tmp_params = $params;
$tmp_params['action'] = 'all';
$all_count = parent::gridDataCount($tmp_params, $model);
$tmp_params['action'] = 'past';
$past_count = parent::gridDataCount($tmp_params, $model);
// The current customer count is simply calculated
// as all customers that are not past customers.
return $all_count - $past_count;
}
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
$links['Customer'] = array('name');
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
@@ -203,17 +178,32 @@ class CustomersController extends AppController {
$this->redirect(array('action'=>'index'));
}
/* //$result = $this->Customer->securityDeposits($id); */
/* $result = $this->Customer->excessPayments($id); */
/* //$result = $this->Customer->unreconciledCharges($id); */
/* echo('<HR>'); */
/* pr($result); */
/* $this->autoRender = false; */
/* return; */
// Get details on this customer, its contacts and leases
$customer = $this->Customer->find
('first', array
('contain' => array
(// Models
'Contact' =>
array('order' => array('Contact.display_name'),
// Models
'ContactPhone',
'ContactEmail',
'ContactAddress',
),
'Lease' =>
array('Unit' =>
array('order' => array('sort_order'),
'fields' => array('id', 'name'),
),
),
),
$customer = $this->Customer->details($id);
'conditions' => array('Customer.id' => $id),
));
//pr($customer);
$outstanding_balance = $customer['stats']['balance'];
// Figure out the outstanding balances for this customer
$outstanding_balance = $this->Customer->balance($id);
$outstanding_deposit = $this->Customer->securityDepositBalance($id);
// Figure out if this customer has any non-closed leases
@@ -254,6 +244,11 @@ class CustomersController extends AppController {
$id));
}
if ($outstanding_balance < 0)
$this->sidemenu_links[] =
array('name' => 'Issue Refund',
'url' => array('action' => 'refund', $id));
// Prepare to render.
$title = 'Customer: ' . $customer['Customer']['name'];
$this->set(compact('customer', 'title',
@@ -388,7 +383,7 @@ class CustomersController extends AppController {
$default_type = $TT->defaultPaymentType();
$this->set(compact('payment_types', 'default_type'));
$title = ($customer['name'] . ': Payment Entry');
$title = ($customer['name'] . ': Receipt Entry');
$this->set(compact('customer', 'charges', 'stats', 'title'));
}
@@ -411,7 +406,7 @@ class CustomersController extends AppController {
));
pr(compact('entries'));
$this->Customer->StatementEntry->reverse($entries);
$this->Customer->refund($entries);
}
@@ -428,8 +423,8 @@ class CustomersController extends AppController {
$this->layout = null;
$this->autoLayout = false;
$this->autoRender = false;
//Configure::write('debug', '0');
//header("Content-type: text/xml;charset=utf-8");
Configure::write('debug', '0');
header("Content-type: text/xml;charset=utf-8");
App::import('Helper', 'Xml');
$xml = new XmlHelper();
@@ -437,16 +432,22 @@ class CustomersController extends AppController {
// Find the unreconciled entries, then manipulate the structure
// slightly to accomodate the format necessary for XML Helper.
$unreconciled = $this->Customer->unreconciledCharges($id);
foreach ($unreconciled['entries'] AS &$entry)
$entry = array_intersect_key($entry['StatementEntry'],
array('id'=>1));
$unreconciled = array('entries' =>
array_intersect_key($unreconciled['debit'],
array('entry'=>1, 'balance'=>1)));
array('entry' => $unreconciled['entries'],
'balance' => $unreconciled['summary']['balance']));
// XML Helper will dump an empty tag if the array is empty
if (!count($unreconciled['entries']['entry']))
if (empty($unreconciled['entries']['entry']))
unset($unreconciled['entries']['entry']);
//pr($unreconciled);
//$reconciled = $cust->reconcileNewStatementEntry($cust_id, 'credit', $amount);
/* pr(compact('unreconciled')); */
/* echo htmlspecialchars($xml->serialize($unreconciled)); */
/* $this->render('/fake'); */
$opts = array();
//$opts['format'] = 'tags';

View File

@@ -64,7 +64,7 @@ class LeasesController extends AppController {
function gridDataFields(&$params, &$model) {
$fields = parent::gridDataFields($params, $model);
return array_merge($fields,
$this->Lease->StatementEntry->chargePaymentFields(true));
$this->Lease->StatementEntry->chargeDisbursementFields(true));
}
function gridDataConditions(&$params, &$model) {
@@ -172,8 +172,6 @@ class LeasesController extends AppController {
);
$this->redirect($this->data['redirect']);
$this->autoRender = false;
return;
}
if (!isset($id))
@@ -213,118 +211,68 @@ class LeasesController extends AppController {
}
/* /\************************************************************************** */
/* ************************************************************************** */
/* ************************************************************************** */
/* * action: promote_credit */
/* * - Moves any lease credit up to the customer level, so that */
/* * it may be used for charges other than those on this lease. */
/* *\/ */
/* function promote_surplus($id) { */
/* $this->Lease->promoteSurplus($id); */
/* pr("PREVENTING REDIRECT"); */
/* $this->render('fake'); */
/* $this->redirect(array('controller' => 'leases', */
/* 'action' => 'view', */
/* $id)); */
/* } */
/**************************************************************************
**************************************************************************
**************************************************************************
* action: apply_deposit
* - Applies the security deposit to charges. This is much
* like a receipt, but it's separated to keep it simple and
* to prevent feature overload on the receipt page.
* action: refund
* - Provides lease customer with a refund
*/
function apply_deposit($id = null) {
// Create some models for convenience
$A = new Account();
if ($this->data) {
// Handle the move out based on the data given
pr($this->data);
$this->Lease->releaseSecurityDeposits($this->data['Lease']['id']);
die();
// Assume this will succeed
$ret = true;
// Go through the entered payments
$receipt_transaction = array_intersect_key($this->data,
array('Transaction'=>1,
'transaction_id'=>1));
foreach ($data['StatementEntry'] AS $entry) {
// Create the receipt entry, and reconcile the credit side
// of the double-entry (which should be A/R) as a payment.
$ids = $this->StatementEntry->Ledger->Account->postLedgerEntry
($receipt_transaction,
array_intersect_key($entry, array('MonetarySource'=>1))
+ array_intersect_key($entry, array('account_id'=>1)),
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()),
'customer_id' => $customer_id,
'lease_id' => $lease_id)
+ $entry,
'receipt');
if ($ids['error'])
$ret = false;
$db = &$model->getDataSource();
$fields = $db->fields($model, $model->alias);
$fields[] = ("SUM(IF(Account.id IS NULL, 0," .
" IF(LedgerEntry.debit_ledger_id = Account.id," .
" 1, -1))" .
" * IF(LedgerEntry.amount IS NULL, 0, LedgerEntry.amount))" .
" AS 'balance'");
return $fields;
$receipt_transaction = array_intersect_key($ids,
array('transaction_id'=>1,
'split_transaction_id'=>1));
}
$this->Lease->moveOut($this->data['Lease']['id'],
'VACANT',
$this->data['Lease']['moveout_date'],
//true // Close this lease, if able
false
);
$this->redirect(array('controller' => 'leases',
'action' => 'view',
$this->data['Lease']['id']));
$this->autoRender = false;
return;
}
$A = new Account();
function refund($id) {
$lease = $this->Lease->find
('first', array
('contain' => array
(// Models
'Unit' =>
array('order' => array('sort_order'),
'fields' => array('id', 'name'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Unit' => array('fields' => array('id', 'name')),
'Customer' => array('fields' => array('id', 'name')),
),
'conditions' => array(array('Lease.id' => $id),
// Make sure lease is not closed...
array('Lease.close_date' => null),
),
));
if (empty($lease)) {
$this->redirect(array('action'=>'view', $id));
}
// Determine the lease balance, bailing if the customer owes money
$balance = $this->Lease->balance($id);
if ($balance >= 0) {
$this->redirect(array('action'=>'view', $id));
}
// Get the lease balance, part of lease stats
$this->Lease->statsMerge($lease['Lease'],
array('stats' => $this->Lease->stats($id)));
// The refund will be for a positive amount
$balance *= -1;
// Determine the lease security deposit
$deposit_balance = $this->Lease->securityDeposits($lease['Lease']['id']);
$this->set(compact('deposit_balance'));
$this->set('customer', $lease['Customer']);
$this->set('unit', $lease['Unit']);
$this->set('lease', $lease['Lease']);
$this->set('account', array('id' => $A->securityDepositAccountID()));
/* $redirect = array('controller' => 'leases', */
/* 'action' => 'view', */
/* $id); */
// Get the accounts capable of paying the refund
$refundAccounts = $this->Lease->StatementEntry->Account->refundAccounts();
$defaultAccount = current($refundAccounts);
$this->set(compact('refundAccounts', 'defaultAccount'));
// Prepare to render
$title = ('Lease #' . $lease['Lease']['number'] . ': ' .
$lease['Unit']['name'] . ': ' .
$lease['Customer']['name'] . ': Utilize Security Deposit');
$this->set(compact('title', 'redirect'));
$lease['Customer']['name'] . ': Refund');
$this->set(compact('title', 'lease', 'balance'));
}
@@ -332,82 +280,39 @@ class LeasesController extends AppController {
**************************************************************************
**************************************************************************
* action: bad_debt
* - Writes off remaining charges on a lease.
* REVISIT <AP>: 20090710
* Should this be a customer function? What customer
* would have only one lease that results in bad debt.
* - Sets up the write-off entry page, so that the
* user can write off remaining charges on a lease.
*/
function bad_debt($id) {
$A = new Account();
$lease = $this->Lease->find
('first', array
('contain' => array
(// Models
'Unit' =>
array('order' => array('sort_order'),
'fields' => array('id', 'name'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Unit' => array('fields' => array('id', 'name')),
'Customer' => array('fields' => array('id', 'name')),
),
'conditions' => array(array('Lease.id' => $id),
// Make sure lease is not closed...
array('Lease.close_date' => null),
),
));
// Make sure we have a valid lease to write off
if (empty($lease))
$this->redirect(array('action' => 'view', $id));
// Get the lease balance, part of lease stats
$this->Lease->statsMerge($lease['Lease'],
array('stats' => $this->Lease->stats($id)));
// Determine the lease security deposit
$deposit_balance = $this->Lease->securityDepositBalance($lease['Lease']['id']);
if ($deposit_balance > 0)
die("Still have un-utilized security deposit");
$this->set('customer', $lease['Customer']);
$this->set('unit', $lease['Unit']);
$this->set('lease', $lease['Lease']);
$this->set('account', array('id' => $A->badDebtAccountID()));
/* $redirect = array('controller' => 'leases', */
/* 'action' => 'view', */
/* $id); */
$stats = $this->Lease->stats($id);
$balance = $stats['balance'];
// Prepare to render
$title = ('Lease #' . $lease['Lease']['number'] . ': ' .
$lease['Unit']['name'] . ': ' .
$lease['Customer']['name'] . ': Write Off Bad Debt');
$this->set(compact('title', 'redirect'));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: refund
* - Provides user with a refund
* REVISIT <AP>: 20090710
* Should this be a customer function?
*/
function refund($id) {
/* // Obtain the overall lease balance */
/* $stats = $this->Lease->stats($id); */
/* $outstanding_balance = $stats['balance']; */
/* // Determine the lease security deposit */
/* $deposits = $this->Lease->securityDeposits($id); */
/* $outstanding_deposit = $deposits['summary']['balance']; */
/* $this->set(compact('lease', 'title', */
/* 'outstanding_deposit', */
/* 'outstanding_balance')); */
$this->set(compact('title', 'lease', 'balance'));
$this->render('/transactions/bad_debt');
}
@@ -455,7 +360,7 @@ class LeasesController extends AppController {
));
$A = new Account();
$charge_accounts = $A->chargeAccounts();
$charge_accounts = $A->invoiceAccounts();
$default_account = $A->rentAccountID();
$this->set(compact('charge_accounts', 'default_account'));
@@ -504,13 +409,9 @@ class LeasesController extends AppController {
$this->set('charge_gaps', $this->Lease->rentChargeGaps($id));
$this->set('charge_through', $this->Lease->rentChargeThrough($id));
// Obtain the overall lease balance
$this->Lease->statsMerge($lease['Lease'],
array('stats' => $this->Lease->stats($id)));
$outstanding_balance = $lease['Lease']['stats']['balance'];
// Determine the lease security deposit
$outstanding_deposit = $this->Lease->securityDepositBalance($lease['Lease']['id']);
// Figure out the outstanding balances for this lease
$outstanding_balance = $this->Lease->balance($id);
$outstanding_deposit = $this->Lease->securityDepositBalance($id);
// Set up dynamic menu items
if (!isset($lease['Lease']['close_date'])) {
@@ -531,19 +432,17 @@ class LeasesController extends AppController {
'action' => 'receipt',
$lease['Customer']['id']));
if (isset($lease['Lease']['moveout_date']) && $outstanding_deposit > 0 && $outstanding_balance > 0)
$this->sidemenu_links[] =
array('name' => 'Apply Deposit', 'url' => array('action' => 'apply_deposit',
$id));
/* if ($outstanding_balance < 0) */
/* $this->sidemenu_links[] = */
/* array('name' => 'Transfer Credit to Customer', */
/* 'url' => array('action' => 'promote_surplus', $id)); */
if (isset($lease['Lease']['moveout_date']) &&
$outstanding_balance <= 0 &&
($outstanding_deposit - $outstanding_balance) > 0)
if ($outstanding_balance < 0)
$this->sidemenu_links[] =
array('name' => 'Issue Refund', 'url' => array('action' => 'refund',
$id));
array('name' => 'Issue Refund',
'url' => array('action' => 'refund', $id));
if (isset($lease['Lease']['moveout_date']) && $outstanding_deposit == 0 && $outstanding_balance > 0)
if (isset($lease['Lease']['moveout_date']) && $outstanding_balance > 0)
$this->sidemenu_links[] =
array('name' => 'Write-Off', 'url' => array('action' => 'bad_debt',
$id));

View File

@@ -16,6 +16,16 @@ class LedgerEntriesController extends AppController {
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: index / current / past / all
* - Creates a list of ledger entries
*/
function index() { $this->gridView('All Ledger Entries'); }
/**************************************************************************
**************************************************************************
**************************************************************************

View File

@@ -16,6 +16,16 @@ class StatementEntriesController extends AppController {
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: index / current / past / all
* - Creates a list of statement entries
*/
function index() { $this->gridView('All Statement Entries'); }
/**************************************************************************
**************************************************************************
**************************************************************************
@@ -49,21 +59,21 @@ class StatementEntriesController extends AppController {
);
if (isset($params['post']['custom']['statement_entry_id'])) {
$link['PaymentEntry'] = array();
$link['DisbursementEntry'] = array();
$link['ChargeEntry'] = array();
}
/* if ($params['action'] === 'collected') { */
/* $link['PaymentEntry'] = array('Receipt' => array('class' => 'Transaction')); */
/* $link['DisbursementEntry'] = array('Receipt' => array('class' => 'Transaction')); */
/* $link['ChargeEntry'] = array('Invoice' => array('class' => 'Transaction')); */
/* } */
/* if (count(array_intersect($params['fields'], array('applied'))) == 1) { */
/* $link['PaymentEntry'] = array(); */
/* $link['DisbursementEntry'] = array(); */
/* $link['ChargeEntry'] = array(); */
/* } */
/* elseif (isset($params['post']['custom']['customer_id']) || isset($params['post']['custom']['lease_id'])) { */
/* $link['PaymentEntry'] = array(); */
/* $link['DisbursementEntry'] = array(); */
/* } */
return array('link' => $link);
@@ -74,18 +84,18 @@ class StatementEntriesController extends AppController {
if (in_array('applied', $params['post']['fields'])) {
$fields[] = ("IF(StatementEntry.type = 'CHARGE'," .
" SUM(COALESCE(PaymentEntry.amount,0))," .
" SUM(COALESCE(DisbursementEntry.amount,0))," .
" SUM(COALESCE(ChargeEntry.amount,0)))" .
" AS 'applied'");
$fields[] = ("StatementEntry.amount - (" .
"IF(StatementEntry.type = 'CHARGE'," .
" SUM(COALESCE(PaymentEntry.amount,0))," .
" SUM(COALESCE(DisbursementEntry.amount,0))," .
" SUM(COALESCE(ChargeEntry.amount,0)))" .
") AS 'balance'");
}
$fields = array_merge($fields,
$this->StatementEntry->chargePaymentFields());
$this->StatementEntry->chargeDisbursementFields());
return $fields;
}
@@ -110,7 +120,7 @@ class StatementEntriesController extends AppController {
if (isset($statement_entry_id)) {
$conditions[] = array('OR' =>
array(array('ChargeEntry.id' => $statement_entry_id),
array('PaymentEntry.id' => $statement_entry_id)));
array('DisbursementEntry.id' => $statement_entry_id)));
}
return $conditions;
@@ -144,13 +154,13 @@ class StatementEntriesController extends AppController {
if (in_array('applied', $params['post']['fields'])) {
$tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1));
$tquery['fields'] = array("IF(StatementEntry.type = 'CHARGE'," .
" SUM(COALESCE(PaymentEntry.amount,0))," .
" SUM(COALESCE(DisbursementEntry.amount,0))," .
" SUM(COALESCE(ChargeEntry.amount,0)))" .
" AS 'applied'",
"StatementEntry.amount - (" .
"IF(StatementEntry.type = 'CHARGE'," .
" SUM(COALESCE(PaymentEntry.amount,0))," .
" SUM(COALESCE(DisbursementEntry.amount,0))," .
" SUM(COALESCE(ChargeEntry.amount,0)))" .
") AS 'balance'",
);
@@ -179,6 +189,19 @@ class StatementEntriesController extends AppController {
function reverse($id) {
$this->StatementEntry->reverse($id);
$this->render('/FAKE');
$this->redirect(array('action'=>'view', $id));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: waive the ledger entry
*/
function waive($id) {
$this->StatementEntry->waive($id);
$this->redirect(array('action'=>'view', $id));
}
@@ -200,7 +223,7 @@ class StatementEntriesController extends AppController {
$entry = $this->StatementEntry->find
('first',
array('contain' => array
('Transaction' => array('fields' => array('id', 'stamp')),
('Transaction' => array('fields' => array('id', 'type', 'stamp')),
'Account' => array('id', 'name', 'type'),
'Customer' => array('fields' => array('id', 'name')),
'Lease' => array('fields' => array('id')),
@@ -211,50 +234,40 @@ class StatementEntriesController extends AppController {
$reconciled = $this->StatementEntry->reconciledEntries($id);
/* // REVISIT <AP>: 20090711 */
/* // It's not clear whether we should be able to reverse charges that have */
/* // already been paid/cleared/reconciled. Certainly, that will be the */
/* // case when someone has pre-paid and then moves out early. However, this */
/* // will work well for items accidentally charged but not yet paid for. */
/* if ((!$entry['DebitLedger']['Account']['trackable'] || */
/* $stats['debit']['amount_reconciled'] == 0) && */
/* (!$entry['CreditLedger']['Account']['trackable'] || */
/* $stats['credit']['amount_reconciled'] == 0) */
/* && 0 */
/* ) */
/* { */
/* // Set up dynamic menu items */
/* $this->sidemenu_links[] = */
/* array('name' => 'Operations', 'header' => true); */
/* $this->sidemenu_links[] = */
/* array('name' => 'Undo', */
/* 'url' => array('action' => 'reverse', */
/* $id)); */
/* } */
/* if ($this->StatementEntry->Ledger->Account->type */
/* ($entry['CreditLedger']['Account']['id']) == 'INCOME') */
/* { */
/* // Set up dynamic menu items */
/* $this->sidemenu_links[] = */
/* array('name' => 'Operations', 'header' => true); */
/* $this->sidemenu_links[] = */
/* array('name' => 'Reverse', */
/* 'url' => array('action' => 'reverse', */
/* $id)); */
/* } */
$stats = $this->StatementEntry->stats($id);
if (strtoupper($entry['StatementEntry']['type']) === 'CHARGE')
if (in_array(strtoupper($entry['StatementEntry']['type']), $this->StatementEntry->debitTypes()))
$stats = $stats['Charge'];
else
$stats = $stats['Payment'];
$stats = $stats['Disbursement'];
if (strtoupper($entry['StatementEntry']['type']) === 'CHARGE') {
$reversal = $this->StatementEntry->find
('first',
array('link' => array('DisbursementEntry'),
'conditions' => array(array('StatementEntry.id' => $id),
array('DisbursementEntry.type' => 'REVERSAL')),
));
// Set up dynamic menu items
if (empty($reversal) || $stats['balance'] > 0)
$this->sidemenu_links[] =
array('name' => 'Operations', 'header' => true);
if (empty($reversal))
$this->sidemenu_links[] =
array('name' => 'Reverse',
'url' => array('action' => 'reverse',
$id));
if ($stats['balance'] > 0)
$this->sidemenu_links[] =
array('name' => 'Waive Balance',
'url' => array('action' => 'waive',
$id));
}
// Prepare to render.
$title = "Statement Entry #{$entry['StatementEntry']['id']}";

View File

@@ -114,7 +114,7 @@ class TransactionsController extends AppController {
**************************************************************************
**************************************************************************
* action: postReceipt
* - handles the creation of a payment receipt
* - handles the creation of a receipt
*/
function postReceipt() {
@@ -261,6 +261,97 @@ class TransactionsController extends AppController {
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: postWriteOff
* - handles the write off of bad debt
*/
function postWriteOff() {
if (!$this->RequestHandler->isPost()) {
echo('<H2>THIS IS NOT A POST FOR SOME REASON</H2>');
return;
}
$data = $this->data;
$data['Entry'][0]['account_id'] =
$this->Transaction->Account->badDebtAccountID();
if (empty($data['Customer']['id']))
$data['Customer']['id'] = null;
if (empty($data['Lease']['id']))
$data['Lease']['id'] = null;
pr(compact('data'));
if (!$this->Transaction->addReceipt($data,
$data['Customer']['id'],
$data['Lease']['id'])) {
$this->Session->setFlash("WRITE OFF FAILED", true);
// REVISIT <AP> 20090706:
// Until we can work out the session problems,
// just die.
die("<H1>WRITE-OFF FAILED</H1>");
}
$this->render('/fake');
// Return to viewing the lease/customer
if (empty($data['Lease']['id']))
$this->redirect(array('controller' => 'customers',
'action' => 'view',
$data['Customer']['id']));
else
$this->redirect(array('controller' => 'leases',
'action' => 'view',
$data['Lease']['id']));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: postRefund
* - handles issuing a customer refund
*/
function postRefund() {
if (!$this->RequestHandler->isPost()) {
echo('<H2>THIS IS NOT A POST FOR SOME REASON</H2>');
return;
}
$data = $this->data;
if (empty($data['Customer']['id']))
$data['Customer']['id'] = null;
if (empty($data['Lease']['id']))
$data['Lease']['id'] = null;
if (!$this->Transaction->addRefund($data,
$data['Customer']['id'],
$data['Lease']['id'])) {
$this->Session->setFlash("REFUND FAILED", true);
// REVISIT <AP> 20090706:
// Until we can work out the session problems,
// just die.
die("<H1>REFUND FAILED</H1>");
}
$this->render('/fake');
// Return to viewing the lease/customer
if (empty($data['Lease']['id']))
$this->redirect(array('controller' => 'customers',
'action' => 'view',
$data['Customer']['id']));
else
$this->redirect(array('controller' => 'leases',
'action' => 'view',
$data['Lease']['id']));
}
/**************************************************************************
**************************************************************************
**************************************************************************

View File

@@ -82,7 +82,7 @@ class UnitsController extends AppController {
$fields = parent::gridDataFields($params, $model);
return array_merge($fields,
$this->Unit->Lease->StatementEntry->chargePaymentFields(true));
$this->Unit->Lease->StatementEntry->chargeDisbursementFields(true));
}
function gridDataConditions(&$params, &$model) {
@@ -236,6 +236,10 @@ class UnitsController extends AppController {
$this->sidemenu_links[] =
array('name' => 'Operations', 'header' => true);
$this->sidemenu_links[] =
array('name' => 'Edit', 'url' => array('action' => 'edit',
$id));
if (isset($unit['CurrentLease']['id']) &&
!isset($unit['CurrentLease']['moveout_date'])) {
$this->sidemenu_links[] =
@@ -249,6 +253,10 @@ class UnitsController extends AppController {
if (isset($unit['CurrentLease']['id']) &&
!isset($unit['CurrentLease']['close_date'])) {
$this->sidemenu_links[] =
array('name' => 'Charge', 'url' => array('controller' => 'leases',
'action' => 'invoice',
$unit['CurrentLease']['id']));
$this->sidemenu_links[] =
array('name' => 'Payment', 'url' => array('controller' => 'customers',
'action' => 'receipt',
@@ -261,4 +269,71 @@ class UnitsController extends AppController {
'outstanding_balance',
'outstanding_deposit'));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: edit
* - Edit unit information
*/
function edit($id = null) {
if (isset($this->data)) {
// Check to see if the operation was cancelled.
if (isset($this->params['form']['cancel'])) {
if (empty($this->data['Unit']['id']))
$this->redirect(array('action'=>'index'));
$this->redirect(array('action'=>'view', $this->data['Unit']['id']));
}
// Make sure we have unit data
if (empty($this->data['Unit']))
$this->redirect(array('action'=>'index'));
// Make sure we have a rental rate
if (empty($this->data['Unit']['rent']))
$this->redirect(array('action'=>'view', $this->data['Unit']['id']));
// Save the unit and all associated data
$this->Unit->create();
$this->Unit->id = $this->data['Unit']['id'];
if (!$this->Unit->save($this->data, false)) {
$this->Session->setFlash("UNIT SAVE FAILED", true);
pr("UNIT SAVE FAILED");
}
$this->redirect(array('action'=>'view', $this->Unit->id));
// For debugging, only if the redirects above have been
// commented out, otherwise this section isn't reached.
$this->render('/fake');
return;
}
if ($id) {
$this->data = $this->Unit->findById($id);
$title = 'Unit ' . $this->data['Unit']['name'] . " : Edit";
}
else {
$title = "Enter New Unit";
$this->data = array();
}
$statusEnums = $this->Unit->allowedStatusSet($id);
$statusEnums = array_combine(array_keys($statusEnums),
array_keys($statusEnums));
$this->set(compact('statusEnums'));
$unit_sizes = $this->Unit->UnitSize->find
('list', array('order' => array('unit_type_id', 'width', 'depth', 'id')));
$this->set(compact('unit_sizes'));
// Prepare to render.
pr($this->data);
$this->set(compact('title'));
}
}

View File

@@ -128,10 +128,12 @@ class Account extends AppModel {
function nsfChargeAccountID() { return $this->nameToID('NSF Charge'); }
function taxAccountID() { return $this->nameToID('Tax'); }
function accountReceivableAccountID() { return $this->nameToID('A/R'); }
function accountPayableAccountID() { return $this->nameToID('A/P'); }
function cashAccountID() { return $this->nameToID('Cash'); }
function checkAccountID() { return $this->nameToID('Check'); }
function moneyOrderAccountID() { return $this->nameToID('Money Order'); }
function concessionAccountID() { return $this->nameToID('Concession'); }
function waiverAccountID() { return $this->nameToID('Waiver'); }
function pettyCashAccountID() { return $this->nameToID('Petty Cash'); }
function invoiceAccountID() { return $this->nameToID('Invoice'); }
function receiptAccountID() { return $this->nameToID('Receipt'); }
@@ -193,18 +195,22 @@ class Account extends AppModel {
* - Returns an array of accounts suitable for activity xxx
*/
function chargeAccounts() {
return $this->relatedAccounts('charges', array('order' => 'name'));
function invoiceAccounts() {
return $this->relatedAccounts('invoices', array('order' => 'name'));
}
function paymentAccounts() {
return $this->relatedAccounts('payments', array('order' => 'name'));
function receiptAccounts() {
return $this->relatedAccounts('receipts', array('order' => 'name'));
}
function depositAccounts() {
return $this->relatedAccounts('deposits', array('order' => 'name'));
}
function refundAccounts() {
return $this->relatedAccounts('refunds', array('order' => 'name'));
}
/**************************************************************************
**************************************************************************
@@ -214,7 +220,7 @@ class Account extends AppModel {
*/
function collectableAccounts() {
$accounts = $this->paymentAccounts();
$accounts = $this->receiptAccounts();
foreach(array($this->nsfAccountID(),
$this->securityDepositAccountID())

View File

@@ -81,12 +81,7 @@ class Customer extends AppModel {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['Customer']))
$query['link']['Customer'] = array();
if (!isset($query['link']['Customer']['fields']))
$query['link']['Customer']['fields'] = array();
$query['conditions'][] = array('Customer.id' => $id);
$query['conditions'][] = array('StatementEntry.customer_id' => $id);
$query['conditions'][] = array('StatementEntry.account_id' =>
$this->StatementEntry->Account->securityDepositAccountID());
@@ -106,18 +101,12 @@ class Customer extends AppModel {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['Lease']))
$query['link']['Lease'] = array();
if (!isset($query['link']['Lease']['fields']))
$query['link']['Lease']['fields'] = array();
$query['conditions'][] = array('Lease.id' => $id);
$query['conditions'][] = array('StatementEntry.customer_id' => $id);
$query['conditions'][] = array('StatementEntry.account_id' =>
$this->StatementEntry->Account->securityDepositAccountID());
$stats = $this->StatementEntry->stats(null, $query);
$balance = $stats['Charge']['reconciled'] - $stats['Payment']['reconciled'];
return $this->prReturn($balance);
return $this->prReturn($stats['account_balance']);
}
@@ -132,92 +121,12 @@ class Customer extends AppModel {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['Customer']))
$query['link']['Customer'] = array();
if (!isset($query['link']['Customer']['fields']))
$query['link']['Customer']['fields'] = array();
/* if (!isset($query['link']['StatementEntry'])) */
/* $query['link']['StatementEntry'] = array(); */
/* if (!isset($query['link']['StatementEntry']['Customer'])) */
/* $query['link']['StatementEntry']['Customer'] = array(); */
/* if (!isset($query['link']['StatementEntry']['Customer']['fields'])) */
/* $query['link']['StatementEntry']['Customer']['fields'] = array(); */
$query['conditions'][] = array('Customer.id' => $id);
$query['conditions'][] = array('StatementEntry.customer_id' => $id);
$set = $this->StatementEntry->reconciledSet('CHARGE', $query, true);
return $this->prReturn($set);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: excessPayments
* - Returns payments which have not yet been fully utilized
*/
function excessPayments($id, $query = null) {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['StatementEntry']))
$query['link']['StatementEntry'] = array();
/* if (!isset($query['link']['StatementEntry']['Customer'])) */
/* $query['link']['StatementEntry']['Customer'] = array(); */
/* if (!isset($query['link']['StatementEntry']['Customer']['fields'])) */
/* $query['link']['StatementEntry']['Customer']['fields'] = array(); */
$query['conditions'][] = array('Customer.id' => $id);
$set = $this->StatementEntry->reconciledSet('PAYMENT', $query, true);
return $this->prReturn($set);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: details
* - Returns detail information for the customer
*/
function details($id = null) {
$this->prEnter(compact('id'));
// Query the DB for need information.
$customer = $this->find
('first', array
('contain' => array
(// Models
'Contact' =>
array('order' => array('Contact.display_name'),
// Models
'ContactPhone',
'ContactEmail',
'ContactAddress',
),
'Lease' =>
array('Unit' =>
array('order' => array('sort_order'),
'fields' => array('id', 'name'),
),
),
),
'conditions' => array('Customer.id' => $id),
));
// 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->securityDeposits($id);
return $this->prReturn($customer);
}
/**************************************************************************
**************************************************************************
**************************************************************************
@@ -294,6 +203,44 @@ class Customer extends AppModel {
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: updateLeaseCount
* - Updates the internal lease count
*/
function updateLeaseCount($id) {
$this->id = $id;
$lease_count =
$this->find('count',
array('link' => array('Lease' => array('type' => 'INNER')),
'conditions' => array('Customer.id' => $id)));
$current_count =
$this->find('count',
array('link' => array('CurrentLease' => array('type' => 'INNER')),
'conditions' => array('Customer.id' => $id)));
$this->saveField('lease_count', $lease_count);
$this->saveField('current_lease_count', $current_count);
$this->saveField('past_lease_count', $lease_count - $current_count);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: balance
* - Returns the balance of money owed on the lease
*/
function balance($id) {
$stats = $this->stats($id);
return $stats['balance'];
}
/**************************************************************************
**************************************************************************
**************************************************************************
@@ -320,14 +267,14 @@ class Customer extends AppModel {
/* $stats = $this->StatementEntry->find */
/* ('first', array */
/* ('contain' => false, */
/* 'fields' => $this->StatementEntry->chargePaymentFields(true), */
/* 'fields' => $this->StatementEntry->chargeDisbursementFields(true), */
/* 'conditions' => array('StatementEntry.customer_id' => $id), */
/* )); */
$find_stats = $this->StatementEntry->find
('first', array
('contain' => false,
'fields' => $this->StatementEntry->chargePaymentFields(true),
'fields' => $this->StatementEntry->chargeDisbursementFields(true),
'conditions' => array('StatementEntry.customer_id' => $id),
));
$find_stats = $find_stats[0];

View File

@@ -12,6 +12,7 @@ class Lease extends AppModel {
'StatementEntry',
);
//var $default_log_level = array('log' => 30, 'show' => 15);
/**************************************************************************
**************************************************************************
@@ -23,21 +24,11 @@ class Lease extends AppModel {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['Lease']))
$query['link']['Lease'] = array();
if (!isset($query['link']['Lease']['fields']))
$query['link']['Lease']['fields'] = array();
$query['conditions'][] = array('Lease.id' => $id);
$query['conditions'][] = array('StatementEntry.lease_id' => $id);
$query['conditions'][] = array('StatementEntry.account_id' =>
$this->StatementEntry->Account->securityDepositAccountID());
$set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true);
/* $set['summary'] = array('total' => $set['summary']['Charge']['total'], */
/* 'balance' => $set['summary']['Charge']['reconciled'], */
/* ); */
return $this->prReturn($set);
}
@@ -53,18 +44,26 @@ class Lease extends AppModel {
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
if (!isset($query['link']['Lease']))
$query['link']['Lease'] = array();
if (!isset($query['link']['Lease']['fields']))
$query['link']['Lease']['fields'] = array();
$query['conditions'][] = array('Lease.id' => $id);
$query['conditions'][] = array('StatementEntry.lease_id' => $id);
$query['conditions'][] = array('StatementEntry.account_id' =>
$this->StatementEntry->Account->securityDepositAccountID());
// REVISIT <AP>: 20090804
// Dilemma... how to handle security deposits used to pay
// charges on the lease, yet are not part of the lease
// security deposit(s)? For example, Lease A & Lease B,
// each with $25 Sec. Dep. Move out of Lease A, and
// promote the lease surplus to the customer. A new
// charge on Lease B for $15, which is assigned $15/$25
// from the promoted Lease A surplus. They way this
// function works at present, it will presume the $15 is
// part of the security deposit balance, and will end up
// calculating it as only $10, which is wrong. Perhaps
// the fix is to release security deposits into some sort
// of income account.
$stats = $this->StatementEntry->stats(null, $query);
$balance = $stats['Charge']['reconciled'] - $stats['Payment']['reconciled'];
return $this->prReturn($balance);
return $this->prReturn($stats['account_balance']);
}
@@ -73,21 +72,18 @@ class Lease extends AppModel {
**************************************************************************
* function: releaseSecurityDeposits
* - Releases all security deposits associated with this lease.
* That simply makes a payment out of them, which can be used
* That simply makes a disbursement out of them, which can be used
* to pay outstanding customer charges, or simply to become
* a customer surplus (customer credit).
*/
function releaseSecurityDeposits($id, $query = null) {
$this->prFunctionLevel(30);
$this->prEnter(compact('id', 'query'));
function releaseSecurityDeposits($id, $stamp = null, $query = null) {
//$this->prFunctionLevel(30);
$this->prEnter(compact('id', 'stamp', 'query'));
$secdeps = $this->securityDeposits($id, $query);
$secdeps = $secdeps['entries'];
$this->pr(20, compact('secdeps'));
$this->securityDepositBalance($id, $query);
die();
// If there are no paid security deposits, then
// we can consider all security deposits released.
if (count($secdeps) == 0)
@@ -95,23 +91,17 @@ class Lease extends AppModel {
// Build a transaction
$release = array('Transaction' => array(), 'Entry' => array());
$release['Transaction']['stamp'] = $stamp;
$release['Transaction']['comment'] = "Security Deposit Release";
foreach ($secdeps AS $charge) {
if ($charge['StatementEntry']['type'] !== 'CHARGE')
die("INTERNAL ERROR: SECURITY DEPOSIT IS NOT CHARGE");
// Since security deposits are being released, this also means
// we're reducing any oustanding amount on a security deposit
// since we no longer expect it to be owed.
// REVISIT <AP>: 20090730
// This is kludgy, and I really don't like it. However, this
// is not presently something that even happens at the moment,
// so this solution will have to work until we come up with
// something more robust, like flagging those charges as defunct.
if ($charge['StatementEntry']['balance'] > 0) {
$this->StatementEntry->id = $charge['StatementEntry']['id'];
$this->StatementEntry->saveField('amount', $charge['StatementEntry']['reconciled']);
}
// any unpaid (or only partially paid) security deposit should
// have the remaining balance reversed.
if ($charge['StatementEntry']['balance'] > 0)
$this->StatementEntry->reverse($charge['StatementEntry']['id'], true, $stamp);
$release['Entry'][] =
array('amount' => $charge['StatementEntry']['reconciled'],
@@ -121,7 +111,7 @@ class Lease extends AppModel {
}
$customer_id = $secdeps[0]['StatementEntry']['customer_id'];
$lease_id = $secdeps[0]['StatementEntry']['lease_id'];
$lease_id = $secdeps[0]['StatementEntry']['lease_id'];
$result = $this->StatementEntry->Transaction->addReceipt
($release, $customer_id, $lease_id);
@@ -345,7 +335,10 @@ class Lease extends AppModel {
// Set the lease number to be the same as the lease ID
$this->id;
$this->saveField('number', $this->id);
// Update the current lease count for the customer
$this->Customer->updateLeaseCount($customer_id);
// Update the unit status
$this->Unit->updateStatus($unit_id, 'OCCUPIED');
@@ -386,10 +379,16 @@ class Lease extends AppModel {
// Save it!
$this->save($this->data, false);
// Release the security deposit(s)
$this->releaseSecurityDeposits($id, $stamp);
// Close the lease, if so requested
if ($close)
$this->close($id, $stamp);
// Update the current lease count for the customer
$this->Customer->updateLeaseCount($this->field('customer_id'));
// Finally, update the unit status
$this->recursive = -1;
$this->read();
@@ -423,6 +422,10 @@ class Lease extends AppModel {
// Save it!
$this->save($this->data, false);
// Update the current lease count for the customer
$this->Customer->updateLeaseCount($this->field('customer_id'));
return $this->prReturn(true);
}
@@ -464,12 +467,45 @@ class Lease extends AppModel {
/**************************************************************************
**************************************************************************
**************************************************************************
* function: addCharge
* - Adds an additional charge to the lease
* function: refund
* - Marks any lease balance as payable to the customer.
*/
function addCharge($id, $charge) {
function refund($id, $stamp = null) {
$this->prEnter(compact('id'));
$balance = $this->balance($id);
if ($balance >= 0)
return $this->prReturn(array('error' => true));
$balance *= -1;
// Build a transaction
$refund = array('Transaction' => array(), 'Entry' => array());
$refund['Transaction']['stamp'] = $stamp;
$refund['Transaction']['comment'] = "Lease Refund";
$refund['Entry'][] =
array('amount' => $balance);
$result = $this->StatementEntry->Transaction->addRefund
($refund, null, $id);
return $this->prReturn($result);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: balance
* - Returns the balance of money owed on the lease
*/
function balance($id) {
$this->prEnter(compact('id'));
$stats = $this->stats($id);
return $this->prReturn($stats['balance']);
}
@@ -485,42 +521,14 @@ class Lease extends AppModel {
if (!$id)
return $this->prReturn(null);
$this->queryInit($query);
//$query['link'] = array('Lease' => $query['link']);
/* if (!isset($query['link']['StatementEntry'])) */
/* $query['link']['StatementEntry'] = array(); */
/* if (!isset($query['link']['StatementEntry']['ChargeEntry'])) */
/* $query['link']['StatementEntry']['ChargeEntry'] = array(); */
/* $query['link']['StatementEntry']['fields'] = array(); */
/* $query['link']['ChargeEntry']['fields'] = array(); */
/* $query['link']['ChargeEntry']['Account']['fields'] = array(); */
/* $query['link']['ChargeEntry']['StatementEntry']['fields'] = array(); */
/* $query['link']['ChargeEntry']['StatementEntry']['Invoice']['fields'] = array(); */
if (!isset($query['fields']))
$query['fields'] = array();
$query['fields'] = array_merge($query['fields'],
$this->StatementEntry->chargePaymentFields(true));
$query['conditions'][] = array('StatementEntry.lease_id' => $id);
$query['group'] = null;
$stats = $this->StatementEntry->find('first', $query);
//$this->pr(20, compact('query', 'stats'));
// The fields are all tucked into the [0] index,
// and the rest of the array is useless (empty).
$stats = $stats[0];
// Make sure we have a non-null balance
if (!isset($stats['balance']))
$stats['balance'] = 0;
return $this->prReturn($stats);
$find_stats = $this->StatementEntry->find
('first', array
('contain' => false,
'fields' => $this->StatementEntry->chargeDisbursementFields(true),
'conditions' => array('StatementEntry.lease_id' => $id),
));
$find_stats = $find_stats[0];
return $this->prReturn($find_stats);
}
}

View File

@@ -170,7 +170,7 @@ class LedgerEntry extends AppModel {
//pr(array('stats()', compact('id', 'query', 'set')));
$rtypes = array('charge', 'payment',
$rtypes = array('charge', 'disbursement',
// 'debit', 'credit',
);
@@ -178,10 +178,8 @@ class LedgerEntry extends AppModel {
foreach($rtypes AS $rtype) {
$Rtype = ucfirst($rtype);
if (($rtype == 'charge' && (!isset($set) || $set == 'PAYMENT')) ||
($rtype == 'payment' && (!isset($set) || $set == 'CHARGE'))
/* ($rtype == 'debit' && (!isset($set) || $set == 'CREDIT')) || */
/* ($rtype == 'credit' && (!isset($set) || $set == 'DEBIT')) */
if (($rtype == 'charge' && (!isset($set) || $set == 'DISBURSEMENT')) ||
($rtype == 'disbursement' && (!isset($set) || $set == 'CHARGE'))
) {
$rquery = $query;

View File

@@ -7,46 +7,79 @@ class StatementEntry extends AppModel {
'Lease',
'Account',
// The charge to which this payment applies (if it is one)
// The charge to which this disbursement applies (if it is one)
'ChargeEntry' => array(
'className' => 'StatementEntry',
),
);
var $hasMany = array(
// The payments that apply to this charge (if it is one)
'PaymentEntry' => array(
// The disbursements that apply to this charge (if it is one)
'DisbursementEntry' => array(
'className' => 'StatementEntry',
'foreignKey' => 'charge_entry_id',
),
);
//var $default_log_level = 30;
var $default_log_level = array('log' => 30, 'show' => 15);
/**************************************************************************
**************************************************************************
**************************************************************************
* function: chargePaymentFields
* function: debit/creditTypes
*/
function debitTypes() {
return array('CHARGE', 'PAYMENT', 'REFUND');
}
function creditTypes() {
return array('DISBURSEMENT', 'WAIVER', 'REVERSAL', 'SURPLUS');
}
function voidTypes() {
return array('VOID');
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: chargeDisbursementFields
*/
function chargeDisbursementFields($sum = false, $entry_name = 'StatementEntry') {
$debits = $this->debitTypes();
$credits = $this->creditTypes();
$voids = $this->voidTypes();
foreach ($debits AS &$enum)
$enum = "'" . $enum . "'";
foreach ($credits AS &$enum)
$enum = "'" . $enum . "'";
foreach ($voids AS &$enum)
$enum = "'" . $enum . "'";
$debit_set = implode(", ", $debits);
$credit_set = implode(", ", $credits);
$void_set = implode(", ", $voids);
function chargePaymentFields($sum = false, $entry_name = 'StatementEntry') {
$fields = array
(
($sum ? 'SUM(' : '') .
"IF({$entry_name}.type = 'CHARGE'," .
"IF({$entry_name}.type IN ({$debit_set})," .
" {$entry_name}.amount, NULL)" .
($sum ? ')' : '') . ' AS charge' . ($sum ? 's' : ''),
($sum ? 'SUM(' : '') .
"IF({$entry_name}.type = 'PAYMENT' OR {$entry_name}.type = 'SURPLUS'," .
"IF({$entry_name}.type IN({$credit_set})," .
" {$entry_name}.amount, NULL)" .
($sum ? ')' : '') . ' AS payment' . ($sum ? 's' : ''),
($sum ? ')' : '') . ' AS disbursement' . ($sum ? 's' : ''),
($sum ? 'SUM(' : '') .
"IF({$entry_name}.type = 'CHARGE', 1," .
" IF({$entry_name}.type = 'PAYMENT' OR {$entry_name}.type = 'SURPLUS', -1, 0))" .
"IF({$entry_name}.type IN ({$debit_set}), 1," .
" IF({$entry_name}.type IN ({$credit_set}), -1, 0))" .
" * IF({$entry_name}.amount, {$entry_name}.amount, 0)" .
($sum ? ')' : '') . ' AS balance',
);
@@ -106,116 +139,128 @@ class StatementEntry extends AppModel {
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: waive
* - Waives the charge balance
*
*/
function waive($id, $stamp = null) {
$this->prEnter(compact('id', 'stamp'));
// Get the basic information about the entry to be waived.
$this->recursive = -1;
$charge = $this->read(null, $id);
$charge = $charge['StatementEntry'];
if ($charge['type'] !== 'CHARGE')
INTERNAL_ERROR("Waiver item is not CHARGE.");
// Query the stats to get the remaining balance
$stats = $this->stats($id);
// Build a transaction
$waiver = array('Transaction' => array(), 'Entry' => array());
$waiver['Transaction']['stamp'] = $stamp;
$waiver['Transaction']['comment'] = "Charge Waiver";
// Add the charge waiver
$waiver['Entry'][] =
array('amount' => $stats['Charge']['balance'],
'account_id' => $this->Account->waiverAccountID(),
'comment' => null,
);
// Record the waiver transaction
return $this->prReturn($this->Transaction->addWaiver
($waiver, $id, $charge['customer_id'], $charge['lease_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) {
$this->prEnter(compact('ledger_entries', 'stamp'));
function reverse($id, $balance = false, $stamp = null) {
$this->prEnter(compact('id', 'balance', '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)));
$ret = array();
$A = new Account();
// Get the basic information about the entry to be reversed.
$this->recursive = -1;
$charge = $this->read(null, $id);
$charge = $charge['StatementEntry'];
$ar_account_id = $A->accountReceivableAccountID();
$receipt_account_id = $A->receiptAccountID();
if ($charge['type'] !== 'CHARGE')
INTERNAL_ERROR("Reversal item is not CHARGE.");
$transaction_id = null;
foreach ($ledger_entries AS $entry) {
$entry = $entry['Entry'];
$amount = -1*$entry['amount'];
// Build a transaction
$reversal = array('Transaction' => array(), 'Entry' => array());
$reversal['Transaction']['stamp'] = $stamp;
$reversal['Transaction']['comment'] = "Credit Note: Charge Reversal";
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 $this->prReturn(null);
$voided_entry_transactions = array();
$reconciled = $this->reconciledEntries($id);
$this->pr(21, compact('reconciled'));
// 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 ($reconciled && !$balance) {
foreach ($reconciled['entries'] AS $entry) {
if ($entry['DisbursementEntry']['type'] === 'REVERSAL')
INTERNAL_ERROR("Charge has already been reversed");
if ($ids['error'])
return $this->prReturn(null);
$transaction_id = $ids['transaction_id'];
/* $voided_entry_transactions[$entry['DisbursementEntry']['transaction_id']] */
/* = array_intersect_key($entry['DisbursementEntry'], */
/* array('customer_id'=>1, 'lease_id'=>1)); */
$this->pr(15, compact('ids', 'amount', 'refund_account_id', 'ar_account_id'),
'Posted Refund Ledger Entry');
/* $reversal['Entry'][] = */
/* array_intersect_key($entry['DisbursementEntry'], */
/* array_flip(array('amount', 'account_id', 'charge_entry_id'))) */
/* + array('type' => 'SURPLUS', */
/* 'comment' => 'Release of funds applied to reversed charge', */
/* ); */
}
/* $this->pr(17, compact('voided_entry_transactions')); */
}
return $this->prReturn(true);
// Query the stats to get the remaining balance
$stats = $this->stats($id);
// Add the charge reversal
$reversal['Entry'][] =
array('charge_entry_id' => $id,
'amount' => -1 * $stats['Charge'][$balance ? 'balance' : 'total'],
'account_id' => $charge['account_id'],
'comment' => 'Charge Reversal',
);
// Record the reversal transaction
$result = $this->Transaction->addReversal
($reversal, $id, $charge['customer_id'], $charge['lease_id']);
$this->pr(21, compact('result'));
$ret['reversal'] = $result;
if ($result['error'])
$ret['error'] = true;
foreach ($voided_entry_transactions AS $transaction_id => $tx) {
$result = $this->assignCredits
(null,
$transaction_id,
null,
null,
$tx['customer_id'],
$tx['lease_id']
);
$this->pr(21, compact('result'));
$ret['assigned'][] = $result;
if ($result['error'])
$ret['error'] = true;
}
return $this->prReturn($ret + array('error' => false));
}
@@ -229,28 +274,21 @@ OPTION 2
function reconciledSetQuery($set, $query) {
$this->queryInit($query);
if ($set == 'CHARGE' || $set == 'PAYMENT')
$query['conditions'][] = array('StatementEntry.type' => $set);
if (in_array($set, $this->debitTypes()))
$query['link']['DisbursementEntry'] = array('fields' => array("SUM(DisbursementEntry.amount) AS reconciled"));
elseif (in_array($set, $this->creditTypes()))
$query['link']['ChargeEntry'] = array('fields' => array("SUM(ChargeEntry.amount) AS reconciled"));
else
die("INVALID RECONCILE SET");
if ($set == 'CHARGE')
$query['link']['PaymentEntry'] = array('fields' => array("SUM(PaymentEntry.amount) AS reconciled"));
if ($set == 'PAYMENT')
$query['link']['ChargeEntry'] = array('fields' => array("SUM(ChargeEntry.amount) AS reconciled"));
$query['conditions'][] = array('StatementEntry.type' => $set);
$query['group'] = 'StatementEntry.id';
// REVISIT: TESTING
//$query['link']['PaymentEntry'] = array('fields' => array("(`PaymentEntry.amount`+0) AS reconciled"));
//$query['group'] = null;
// END REVISIT
return $query;
}
function reconciledSet($set, $query = null, $unrec = false, $if_rec_include_partial = false) {
//$this->prFunctionLevel(16);
//$this->prFunctionLevel(array('log' => 16, 'show' => 10));
$this->prEnter(compact('set', 'query', 'unrec', 'if_rec_include_partial'));
$lquery = $this->reconciledSetQuery($set, $query);
$result = $this->find('all', $lquery);
@@ -274,7 +312,7 @@ OPTION 2
$reconciled = true;
elseif ($entry['StatementEntry']['reconciled'] == 0)
$reconciled = false;
else // Partial payment; depends on unrec
else // Partial disbursement; depends on unrec
$reconciled = (!$unrec && $if_rec_include_partial);
// Add to the set, if it's been requested
@@ -292,7 +330,7 @@ OPTION 2
**************************************************************************
* function: reconciledEntries
* - Returns a list of entries that reconcile against the given entry.
* (such as payments towards a charge).
* (such as disbursements towards a charge).
*/
function reconciledEntriesQuery($id, $query = null) {
$this->queryInit($query, false);
@@ -303,10 +341,14 @@ OPTION 2
$query['conditions'][] = array('StatementEntry.id' => $id);
if ($this->data['StatementEntry']['type'] == 'CHARGE')
$query['link']['PaymentEntry'] = array();
if ($this->data['StatementEntry']['type'] == 'PAYMENT')
if (in_array($this->data['StatementEntry']['type'], $this->debitTypes())) {
$query['link']['DisbursementEntry'] = array();
$query['conditions'][] = array('DisbursementEntry.id !=' => null);
}
if (in_array($this->data['StatementEntry']['type'], $this->creditTypes())) {
$query['link']['ChargeEntry'] = array();
$query['conditions'][] = array('ChargeEntry.id !=' => null);
}
return $query;
}
@@ -331,60 +373,121 @@ OPTION 2
*
* REVISIT <AP>: 20090726
* This algorithm shouldn't be hardcoded. We need to allow
* the user to specify how payments should be applied.
* the user to specify how disbursements should be applied.
*
*/
function assignCredits($query = null, $receipt_id = null) {
function assignCredits($query = null, $receipt_id = null,
$charge_ids = null, $disbursement_type = null,
$customer_id = null, $lease_id = null)
{
//$this->prFunctionLevel(25);
$this->prEnter( compact('query', 'receipt_id'));
$this->prEnter(compact('query', 'receipt_id',
'charge_ids', 'disbursement_type',
'customer_id', 'lease_id'));
$this->queryInit($query);
if (!empty($customer_id))
$query['conditions'][] = array('StatementEntry.customer_id' => $customer_id);
if (empty($disbursement_type))
$disbursement_type = 'DISBURSEMENT';
$ret = array();
// First, find all known credits
$lquery = $query;
$lquery['conditions'][] = array('StatementEntry.type' => 'SURPLUS');
$lquery['order'][] = 'StatementEntry.effective_date ASC';
$credits = $this->find('all', $lquery);
$this->pr(18, compact('credits'),
"Credits Established");
// First, find all known credits, unless this call is to make
// credit adjustments to a specific charge
// REVISIT <AP>: 20090806
// If the theory below is correct, we should only search for
// explicit credits if we don't have a receipt_id
if (empty($charge_ids)) {
$lquery = $query;
$lquery['conditions'][] = array('StatementEntry.type' => 'SURPLUS');
// REVISIT <AP>: 20090804
// We need to ensure that we're using surplus credits ONLY from either
// the given lease, or those that do not apply to any specific lease.
// However, by doing this, it forces any lease surplus amounts to
// remain frozen with that lease until either there is a lease charge,
// we refund the money, or we "promote" that surplus to the customer
// level and out of the leases direct control.
// That seems like a pain. Perhaps we should allow any customer
// surplus to be used on any customer charge.
$lquery['conditions'][] =
array('OR' =>
array(array('StatementEntry.lease_id' => null),
(!empty($lease_id)
? array('StatementEntry.lease_id' => $lease_id)
: array()),
));
$lquery['order'][] = 'StatementEntry.effective_date ASC';
$credits = $this->find('all', $lquery);
$this->pr(18, compact('credits'),
"Credits Established");
}
else {
if (empty($receipt_id))
INTERNAL_ERROR("Can't make adjustments to a charge without a receipt ID.");
}
// Next, establish credit from the newly added receipt
$receipt_credit = null;
if (!empty($receipt_id)) {
$lquery = $query;
$lquery['link'] += array('LedgerEntry' =>
array('conditions' =>
//array(LedgerEntry.'crdr'=>'DEBIT'),
array('LedgerEntry.account_id !=' => $this->Account->accountReceivableAccountID()),
));
$lquery['fields'] = array('Transaction.id', 'Transaction.stamp', 'Transaction.amount',
'LedgerEntry.account_id');
// Very specific case here... no extra conditions
unset($lquery['conditions']);
$this->Transaction->id = $receipt_id;
$lquery =
array('link' =>
array('StatementEntry',
'LedgerEntry' =>
array('conditions' =>
array('LedgerEntry.account_id !=' =>
$this->Account->accountReceivableAccountID()),
),
),
'conditions' => array('Transaction.id' => $receipt_id),
'fields' => array('Transaction.id', 'Transaction.stamp', 'Transaction.amount'),
);
$receipt_credit = $this->Transaction->find('first', $lquery);
if (!$receipt_credit)
die("INTERNAL ERROR: UNABLE TO LOCATE RECEIPT");
INTERNAL_ERROR("Unable to locate receipt.");
$receipt_credit['balance'] = $receipt_credit['Transaction']['amount'];
//$reconciled = $this->reconciledEntries($id);
$stats = $this->Transaction->stats($receipt_id);
$receipt_credit['balance'] =
$receipt_credit['Transaction']['amount'] - $stats['Disbursement']['total'];
$this->pr(18, compact('receipt_credit'),
"Receipt Credit Added");
}
// Now find all unpaid charges
$lquery = $query;
if (isset($charge_ids)) {
$lquery = array('contain' => false,
'conditions' => array('StatementEntry.id' => $charge_ids));
} else {
$lquery = $query;
// If we're working with a specific lease, then limit charges to it
if (!empty($lease_id))
$lquery['conditions'][] = array('StatementEntry.lease_id' => $lease_id);
}
$lquery['order'] = 'StatementEntry.effective_date ASC';
$charges = $this->reconciledSet('CHARGE', $lquery, true);
$this->pr(18, compact('charges'),
"Outstanding Charges Determined");
$charges = array();
foreach ($this->debitTypes() AS $dtype) {
$rset = $this->reconciledSet($dtype, $lquery, true);
$entries = $rset['entries'];
$charges = array_merge($charges, $entries);
$this->pr(18, compact('dtype', 'entries'), "Outstanding Debit Entries");
}
// Initialize our list of used credits
$used_credits = array();
// Work through all unpaid charges, applying payments as we go
foreach ($charges['entries'] AS $charge) {
// REVISIT <AP>: 20090806
// Testing a theory. We should never have
// explicit credits, as well as a new receipt,
// and yet have outstanding charges.
if (!empty($credits) && !empty($receipt_credit) && !empty($charges))
INTERNAL_ERROR("Explicit credits that haven't already been applied.");
// Work through all unpaid charges, applying disbursements as we go
foreach ($charges AS $charge) {
$this->pr(20, compact('charge'),
'Process Charge');
@@ -394,26 +497,26 @@ OPTION 2
// will handle everything just fine. However, this
// just saves extra processing if/when there is no
// means to resolve a charge anyway.
if (count($credits) == 0 && empty($receipt_credit['balance'])) {
$this->pr(15, 'No more available credits');
if (empty($credits) && empty($receipt_credit['balance'])) {
$this->pr(17, 'No more available credits');
break;
}
$charge['balance'] = $charge['StatementEntry']['balance'];
while ($charge['balance'] > 0 &&
(count($credits) || !empty($receipt_credit['balance']))) {
(!empty($credits) || !empty($receipt_credit['balance']))) {
$this->pr(20, compact('charge'),
'Attempt Charge Reconciliation');
// Use explicit credits before using implicit credits
// (Not sure it matters though).
if (count($credits)) {
if (!empty($credits)) {
// Peel off the first credit available
$credit =& $credits[0];
$payment_date = $credit['StatementEntry']['effective_date'];
$payment_transaction_id = $credit['StatementEntry']['transaction_id'];
$payment_account_id = $credit['StatementEntry']['account_id'];
$disbursement_date = $credit['StatementEntry']['effective_date'];
$disbursement_transaction_id = $credit['StatementEntry']['transaction_id'];
$disbursement_account_id = $credit['StatementEntry']['account_id'];
if (!isset($credit['balance']))
$credit['balance'] = $credit['StatementEntry']['amount'];
@@ -421,57 +524,57 @@ OPTION 2
elseif (!empty($receipt_credit['balance'])) {
// Use our only receipt credit
$credit =& $receipt_credit;
$payment_date = $credit['Transaction']['stamp'];
$payment_transaction_id = $credit['Transaction']['id'];
$payment_account_id = $credit['LedgerEntry']['account_id'];
$disbursement_date = $credit['Transaction']['stamp'];
$disbursement_transaction_id = $credit['Transaction']['id'];
$disbursement_account_id = $credit['LedgerEntry']['account_id'];
}
else {
die("HOW DID WE GET HERE WITH NO SURPLUS?");
}
// Set the payment amount to the maximum amount
// Set the disbursement amount to the maximum amount
// possible without exceeding the charge or credit balance
$payment_amount = min($charge['balance'], $credit['balance']);
$disbursement_amount = min($charge['balance'], $credit['balance']);
if (!isset($credit['applied']))
$credit['applied'] = 0;
$credit['applied'] += $payment_amount;
$credit['balance'] -= $payment_amount;
$credit['applied'] += $disbursement_amount;
$credit['balance'] -= $disbursement_amount;
$this->pr(20, compact('credit'),
($credit['balance'] > 0 ? 'Utilized' : 'Exhausted') .
(count($credits) ? ' Credit' : ' Receipt'));
(!empty($credits) ? ' Credit' : ' Receipt'));
if ($credit['balance'] < 0)
die("HOW DID WE END UP WITH NEGATIVE SURPLUS BALANCE?");
// If we've exhausted the credit, get it out of the
// available credit pool (but keep track of it for later).
if ($credit['balance'] <= 0 && count($credits))
if ($credit['balance'] <= 0 && !empty($credits))
$used_credits[] = array_shift($credits);
// Add a payment that uses the available credit to pay the charge
$payment = array('type' => 'PAYMENT',
'account_id' => $payment_account_id,
'amount' => $payment_amount,
'effective_date' => $payment_date,
'transaction_id' => $payment_transaction_id,
// Add a disbursement that uses the available credit to pay the charge
$disbursement = array('type' => $disbursement_type,
'account_id' => $disbursement_account_id,
'amount' => $disbursement_amount,
'effective_date' => $disbursement_date,
'transaction_id' => $disbursement_transaction_id,
'customer_id' => $charge['StatementEntry']['customer_id'],
'lease_id' => $charge['StatementEntry']['lease_id'],
'charge_entry_id' => $charge['StatementEntry']['id'],
'comment' => null,
);
$this->pr(20, compact('payment'),
'New Payment Entry');
$this->pr(20, compact('disbursement'),
'New Disbursement Entry');
$result = $this->addStatementEntry($payment);
$ret['Payment'][] = $result;
$result = $this->addStatementEntry($disbursement);
$ret['Disbursement'][] = $result;
if ($result['error'])
$ret['error'] = true;
// Adjust the charge balance to reflect the new payment
$charge['balance'] -= $payment_amount;
// Adjust the charge balance to reflect the new disbursement
$charge['balance'] -= $disbursement_amount;
if ($charge['balance'] < 0)
die("HOW DID WE GET A NEGATIVE CHARGE AMOUNT?");
@@ -486,7 +589,7 @@ OPTION 2
$used_credits[] = array_shift($credits);
$this->pr(18, compact('credits', 'used_credits', 'receipt_credit'),
'Payments added');
'Disbursements added');
// Clean up any explicit credits that have been used
foreach ($used_credits AS $credit) {
@@ -509,20 +612,37 @@ OPTION 2
if (!empty($receipt_credit['balance'])) {
$credit =& $receipt_credit;
$this->pr(18, compact('credit'),
'Create Explicit Credit');
$explicit_credit = $this->find
('first', array('contain' => false,
'conditions' =>
array(array('transaction_id' => $credit['Transaction']['id']),
array('type' => 'SURPLUS')),
));
$result = $this->addStatementEntry
(array('type' => 'SURPLUS',
'account_id' => $credit['LedgerEntry']['account_id'],
'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;
if (empty($explicit_credit)) {
$this->pr(18, compact('credit'),
'Create Explicit Credit');
$result = $this->addStatementEntry
(array('type' => 'SURPLUS',
'account_id' => $credit['LedgerEntry']['account_id'],
'amount' => $credit['balance'],
'effective_date' => $credit['Transaction']['stamp'],
'transaction_id' => $credit['Transaction']['id'],
'customer_id' => $customer_id,
'lease_id' => $lease_id,
));
$ret['Credit'] = $result;
if ($result['error'])
$ret['error'] = true;
}
else {
$this->pr(18, compact('explicit_credit', 'credit'),
'Update Explicit Credit');
$EC = new StatementEntry();
$EC->id = $explicit_credit['StatementEntry']['id'];
$EC->saveField('amount', $credit['balance']);
}
}
return $this->prReturn($ret + array('error' => false));
@@ -536,6 +656,7 @@ OPTION 2
* - Returns summary data from the requested statement entry
*/
function stats($id = null, $query = null) {
$this->prFunctionLevel(array('log' => 19, 'show' => 10));
$this->prEnter(compact('id', 'query'));
$this->queryInit($query);
@@ -545,48 +666,68 @@ OPTION 2
if (isset($id))
$query['conditions'][] = array('StatementEntry.id' => $id);
$rquery = $query;
unset($rquery['link']['ChargeEntry']);
$rquery['link']['PaymentEntry'] = array('fields' => array());
$types = array('Charge', 'Disbursement');
foreach ($types AS $type_index => $this_name) {
$that_name = $types[($type_index + 1) % 2];
if ($this_name === 'Charge') {
$this_types = $this->debitTypes();
$that_types = $this->creditTypes();
} else {
$this_types = $this->creditTypes();
$that_types = $this->debitTypes();
}
$rquery['fields'] = array();
$rquery['fields'][] = "StatementEntry.amount";
$rquery['fields'][] = "SUM(PaymentEntry.amount) AS reconciled";
$this_query = $query;
$this_query['fields'] = array();
$this_query['fields'][] = "SUM(StatementEntry.amount) AS total";
$this_query['conditions'][] = array('StatementEntry.type' => $this_types);
$result = $this->find('first', $this_query);
$stats[$this_name] = $result[0];
$rquery['conditions'][] = array('StatementEntry.type' => 'CHARGE');
$rquery['group'] = 'StatementEntry.id';
$this->pr(17, compact('this_query', 'result'), $this_name.'s');
$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'];
// Tally the different types that result in credits towards the charges
$stats[$this_name]['reconciled'] = 0;
foreach ($that_types AS $that_type) {
$lc_that_type = strtolower($that_type);
$that_query = $this_query;
$that_query['link']["{$that_name}Entry"] = array('fields' => array());
$that_query['fields'] = array();
if ($this_name == 'Charge')
$that_query['fields'][] = "COALESCE(SUM(${that_name}Entry.amount),0) AS $lc_that_type";
else
$that_query['fields'][] = "COALESCE(SUM(StatementEntry.amount), 0) AS $lc_that_type";
$that_query['conditions'][] = array("{$that_name}Entry.type" => $that_type);
$result = $this->find('first', $that_query);
$stats[$this_name] += $result[0];
$this->pr(17, compact('that_query', 'result'), "{$this_name}s: $that_type");
$stats[$this_name]['reconciled'] += $stats[$this_name][$lc_that_type];
}
// Compute balance information for charges
$stats[$this_name]['balance'] =
$stats[$this_name]['total'] - $stats[$this_name]['reconciled'];
if (!isset($stats[$this_name]['balance']))
$stats[$this_name]['balance'] = 0;
}
$stats['Charge']['balance'] =
$stats['Charge']['total'] - $stats['Charge']['reconciled'];
$this->pr(17, compact('query', 'result'),
'Charges');
$rquery = $query;
unset($rquery['link']['PaymentEntry']);
$rquery['link']['ChargeEntry'] = array('fields' => array());
// 'balance' is simply the difference between
// the balances of charges and disbursements
$stats['balance'] = $stats['Charge']['balance'] - $stats['Disbursement']['balance'];
if (!isset($stats['balance']))
$stats['balance'] = 0;
$rquery['fields'] = array();
$rquery['fields'][] = "SUM(StatementEntry.amount) AS total";
$rquery['fields'][] = "SUM(IF(ChargeEntry.id IS NULL, 0, StatementEntry.amount)) AS reconciled";
$rquery['fields'][] = "SUM(IF(ChargeEntry.id IS NULL, StatementEntry.amount, 0)) AS balance";
$rquery['conditions'][] = array('StatementEntry.type' => 'PAYMENT');
$result = $this->find('first', $rquery);
if (!isset($result[0]['balance']))
$result[0]['balance'] = 0;
$stats['Payment'] = $result[0];
$this->pr(17, compact('rquery', 'result'),
'Payments');
// 'account_balance' is really only relevant to
// callers that have requested charge and disbursement
// stats with respect to a particular account.
// It represents the difference between inflow
// and outflow from that account.
$stats['account_balance'] = $stats['Charge']['reconciled'] - $stats['Disbursement']['total'];
if (!isset($stats['account_balance']))
$stats['account_balance'] = 0;
return $this->prReturn($stats);
}
}
}

View File

@@ -92,8 +92,8 @@ class Tender extends AppModel {
* - Add new LedgerEntry (L2a); T1; debit:NSF; -$A; N1
* - Add new LedgerEntry (L2b); T1; credit:A/R; -$A
* - For Tx associated with LE associated with C1:
* - For each Payment SE of Tx:
* - Add new StatementEntry (S1n); T1; PAYMENT; -1*S1n.amount
* - For each Disbursement SE of Tx:
* - Add new StatementEntry (S1n); T1; DISBURSEMENT; -1*S1n.amount
* - New Transaction (T2) (?????)
* - Add new StatementEntry (S2); T2; CHARGE; NSF; $35
* - Add new LedgerEntry (L3a); T2; credit:NSF-Fee; $35

View File

@@ -112,4 +112,4 @@ class TenderType extends AppModel {
return $stats[0];
}
}
}

View File

@@ -19,9 +19,9 @@ class Transaction extends AppModel {
'conditions' => array('Charge.type' => 'CHARGE')
),
'Payment' => array(
'Disbursement' => array(
'className' => 'StatementEntry',
'conditions' => array('Payment.type' => 'PAYMENT')
'conditions' => array('Disbursement.type' => 'DISBURSEMENT')
),
'Debit' => array(
@@ -37,7 +37,7 @@ class Transaction extends AppModel {
);
var $default_log_level = 30;
var $default_log_level = array('log' => 30, 'show' => 30);
/**************************************************************************
**************************************************************************
@@ -51,23 +51,25 @@ class Transaction extends AppModel {
// Establish the transaction as an invoice
$invoice =& $data['Transaction'];
$invoice['type'] = 'INVOICE';
$invoice['crdr'] = 'DEBIT';
$invoice['account_id'] = $this->Account->accountReceivableAccountID();
$invoice['customer_id'] = $customer_id;
$invoice['lease_id'] = $lease_id;
$invoice +=
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['type'] = 'CHARGE';
$entry['crdr'] = 'CREDIT';
}
foreach ($data['Entry'] AS &$entry)
$entry += array('type' => 'CHARGE',
'crdr' => 'CREDIT',
);
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
if (isset($ids['transaction_id']))
$ids['invoice_id'] = $ids['transaction_id'];
return $ids;
return $this->prReturn($ids);
}
@@ -83,26 +85,30 @@ class Transaction extends AppModel {
// Establish the transaction as a receipt
$receipt =& $data['Transaction'];
$receipt['type'] = 'RECEIPT';
$receipt['crdr'] = 'CREDIT';
$receipt['account_id'] = $this->Account->accountReceivableAccountID();
$receipt['customer_id'] = $customer_id;
$receipt['lease_id'] = $lease_id;
$receipt +=
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 payments
foreach ($data['Entry'] AS &$entry) {
$entry['crdr'] = 'DEBIT';
if (empty($entry['account_id']) && isset($entry['Tender']['tender_type_id'])) {
$entry['account_id'] = $this->LedgerEntry->Tender->TenderType->
accountID($entry['Tender']['tender_type_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['Transaction'], $data['Entry']);
if (isset($ids['transaction_id']))
$ids['receipt_id'] = $ids['transaction_id'];
return $ids;
return $this->prReturn($ids);
}
@@ -113,36 +119,46 @@ class Transaction extends AppModel {
* - Adds a new waiver
*/
function addWaiver($data, $customer_id, $lease_id = null) {
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
// REVISIT <AP>: 20090802
// Completely un-implemented. Just copied from addReceipt
// and search-replace receipt with waiver.
return ('error' => true);
function addWaiver($data, $charge_id, $customer_id, $lease_id = null) {
$this->prEnter(compact('data', 'charge_id', 'customer_id', 'lease_id'));
// Establish the transaction as a waiver
$waiver =& $data['Transaction'];
$waiver['type'] = 'RECEIPT';
$waiver['crdr'] = 'CREDIT';
$waiver['account_id'] = $this->Account->accountReceivableAccountID();
$waiver['customer_id'] = $customer_id;
$waiver['lease_id'] = $lease_id;
if (count($data['Entry']) != 1)
INTERNAL_ERROR("Should be one Entry for addWaiver");
// Go through the statement entries and flag as payments
foreach ($data['Entry'] AS &$entry) {
$entry['type'] = 'WAIVE';
$entry['crdr'] = 'DEBIT';
if (empty($entry['account_id']) && isset($entry['Tender']['tender_type_id'])) {
$entry['account_id'] = $this->LedgerEntry->Tender->TenderType->
accountID($entry['Tender']['tender_type_id']);
}
}
// Just make sure the disbursement(s) are marked as waivers
// and that they go to cover the specific charge.
$data['Transaction']['disbursement_type'] = 'WAIVER';
$data['Transaction']['assign_charge_entry_id'] = $charge_id;
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
// 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 $ids;
return $this->prReturn($ids);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: addReversal
* - Adds a new charge reversal
*/
function addReversal($data, $charge_id, $customer_id, $lease_id = null) {
$this->prEnter(compact('data', 'charge_id', 'customer_id', 'lease_id'));
$data['Transaction'] += array('type' => 'CREDIT_NOTE');
foreach ($data['Entry'] AS &$entry)
$entry += array('type' => 'REVERSAL');
// In all other respects this is just an invoice
$ids = $this->addInvoice($data, $customer_id, $lease_id);
if (isset($ids['transaction_id']))
$ids['reversal_id'] = $ids['transaction_id'];
return $this->prReturn($ids);
}
@@ -158,11 +174,13 @@ class Transaction extends AppModel {
// Establish the transaction as a deposit
$deposit =& $data['Transaction'];
$deposit['type'] = 'DEPOSIT';
$deposit['crdr'] = 'DEBIT';
$deposit['account_id'] = $account_id;
$deposit['customer_id'] = null;
$deposit['lease_id'] = null;
$deposit +=
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.
@@ -192,7 +210,7 @@ class Transaction extends AppModel {
);
}
return $ids;
return $this->prReturn($ids);
}
@@ -208,10 +226,13 @@ class Transaction extends AppModel {
// Establish the transaction as a close
$close =& $data['Transaction'];
$close['type'] = 'CLOSE';
$close['account_id'] = null;
$close['customer_id'] = null;
$close['lease_id'] = null;
$close +=
array('type' => 'CLOSE',
'crdr' => null,
'account_id' => null,
'customer_id' => null,
'lease_id' => null,
);
$ledger_ids = array();
$data['Entry'] = array();
@@ -249,7 +270,73 @@ class Transaction extends AppModel {
);
}
return $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'));
// 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.
$refund =& $data['Transaction'];
$refund +=
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'));
// Establish the transaction as an payment
$payment =& $data['Transaction'];
$payment +=
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['Transaction'], $data['Entry']);
if (isset($ids['transaction_id']))
$ids['payment_id'] = $ids['transaction_id'];
return $this->prReturn($ids);
}
@@ -322,7 +409,7 @@ class Transaction extends AppModel {
*
* - Entry (array)
* - [MANDATORY]
* - type (CHARGE, PAYMENT)
* - type (CHARGE, DISBURSEMENT)
* - account_id
* - crdr
* - amount
@@ -435,16 +522,19 @@ class Transaction extends AppModel {
array_flip(array('customer_id', 'lease_id')));
$se['comment'] = $entry['statement_entry_comment'];
// (PAYMENTS will have statement entries created below, when
// (DISBURSEMENTS will have statement entries created below, when
// assigning credits, and DEPOSITS don't have statement entries)
if ($transaction['type'] != 'INVOICE' && $subtype !== 'NSF')
if (empty($entry['charge_entry_id']) &&
(empty($transaction['customer_id']) ||
($transaction['account_id'] == $this->Account->accountReceivableAccountID() &&
$transaction['crdr'] == 'CREDIT')))
$se = null;
// NSF transactions don't use LedgerEntries
// REVISIT <AP>: 20090731
// Doesn't seem right... probably doing this because of the
// single A/R entry we add below for NSF
if ($subtype === 'NSF')
if ($entry['type'] == 'SURPLUS' || $subtype === 'NSF')
$le1 = $le1_tender = $le2 = null;
// Replace combined entry with our new individual entries
@@ -476,6 +566,7 @@ class Transaction extends AppModel {
$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;
@@ -503,6 +594,7 @@ class Transaction extends AppModel {
if (!empty($se)) {
$se['transaction_id'] = $ret['transaction_id'];
$se += array('effective_date' => $transaction_stamp);
$result = $this->StatementEntry->addStatementEntry($se);
$ret['entries'][$e_index]['StatementEntry'] = $result;
if ($result['error']) {
@@ -512,15 +604,22 @@ class Transaction extends AppModel {
}
}
if (($transaction['type'] == 'INVOICE' ||
$transaction['type'] == 'RECEIPT') &&
$subtype !== 'NSF' && !$ret['error']) {
if ($transaction['account_id'] == $this->Account->accountReceivableAccountID()
&& !$ret['error']) {
$result = $this->StatementEntry->assignCredits
(array('link' => array('Customer'),
'conditions' => array('Customer.id' => $transaction['customer_id'])),
($transaction['type'] == 'RECEIPT'
(null,
($transaction['crdr'] == 'CREDIT'
? $ret['transaction_id']
: null));
: null),
($transaction['crdr'] == 'CREDIT' && !empty($transaction['assign_charge_entry_id'])
? $transaction['assign_charge_entry_id']
: null),
(!empty($transaction['disbursement_type'])
? $transaction['disbursement_type']
: null),
$transaction['customer_id'],
$transaction['lease_id']
);
$ret['assigned'] = $result;
if ($result['error'])
@@ -546,7 +645,7 @@ class Transaction extends AppModel {
// 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, payments, credits, etc).
// with the customer statement (charges, disbursements, credits, etc).
$bounce_result = $this->addDeposit
(array('Transaction' => array(),
'Entry' => array(array('tender_id' => null,
@@ -596,20 +695,20 @@ class Transaction extends AppModel {
$rollback['Transaction']['customer_id'] = $tender['Tender']['customer_id'];
$rollback['Transaction']['amount'] = -1 * $tender['LedgerEntry']['amount'];
foreach ($nsf_ledger_entry['Transaction']['StatementEntry'] AS $payment) {
if ($payment['type'] === 'SURPLUS') {
$payment['type'] = 'VOID';
$this->StatementEntry->id = $payment['id'];
$this->StatementEntry->saveField('type', $payment['type']);
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' => $payment['type'],
'amount' => -1 * $payment['amount'],
array('type' => $disbursement['type'],
'amount' => -1 * $disbursement['amount'],
'account_id' => $this->Account->nsfAccountID(),
'customer_id' => $payment['customer_id'],
'lease_id' => $payment['lease_id'],
'charge_entry_id' => $payment['charge_entry_id'],
'customer_id' => $disbursement['customer_id'],
'lease_id' => $disbursement['lease_id'],
'charge_entry_id' => $disbursement['charge_entry_id'],
'effective_date' => $stamp,
);
}
@@ -617,11 +716,13 @@ class Transaction extends AppModel {
// Record the transaction, which will un-pay previously paid
// charges, void any credits, and other similar work.
$rollback_result = $this->addTransaction($rollback['Transaction'], $rollback['Entry'], 'NSF');
$this->pr(20, compact('rollback', 'rollback_result'));
$ret['rollback'] = $rollback_result;
if ($rollback_result['error'])
return $this->prReturn(array('error' => true) + $ret);
if (count($rollback['Entry'])) {
$rollback_result = $this->addTransaction($rollback['Transaction'], $rollback['Entry'], 'NSF');
$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
@@ -647,7 +748,8 @@ class Transaction extends AppModel {
return $this->prReturn(array('error' => true) + $ret);
$ret['nsf_transaction_id'] = $ret['bounce']['transaction_id'];
$ret['nsf_ledger_entry_id'] = $ret['rollback']['entries'][0]['DoubleEntry']['Entry1']['ledger_entry_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));
}
@@ -698,7 +800,7 @@ class Transaction extends AppModel {
}
elseif ($table == 'StatementEntry') {
$squery['fields'] = array_merge($squery['fields'],
$this->StatementEntry->chargePaymentFields(true));
$this->StatementEntry->chargeDisbursementFields(true));
}
else {
$squery['fields'][] = "SUM({$table}.amount) AS total";

View File

@@ -27,6 +27,8 @@ class Unit extends AppModel {
'Lease',
);
//var $default_log_level = array('log' => 30, 'show' => 15);
/**************************************************************************
**************************************************************************
**************************************************************************
@@ -50,7 +52,7 @@ class Unit extends AppModel {
}
function occupiedEnumValue() {
return statusValue('OCCUPIED');
return $this->statusValue('OCCUPIED');
}
function conditionOccupied() {
@@ -68,18 +70,71 @@ class Unit extends AppModel {
return ('Unit.status <= ' . $this->statusValue('UNAVAILABLE'));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: allowedStatusSet
* - Returns the status set allowed for the given unit
*/
function allowedStatusSet($id) {
$this->prEnter(compact('id'));
$this->id = $id;
$old_status = $this->field('status');
$old_val = $this->statusValue($old_status);
$this->pr(17, compact('old_status', 'old_val'));
$enums = $this->activeStatusEnums();
$this->pr(21, compact('enums'));
foreach ($enums AS $enum => $val) {
if (($old_val < $this->occupiedEnumValue()) !=
($val < $this->occupiedEnumValue())) {
unset($enums[$enum]);
}
}
return $this->prReturn($enums);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: updateStatus
* - Update the given unit to the given status
*/
function updateStatus($id, $status) {
function updateStatus($id, $status, $check = false) {
$this->prEnter(compact('id', 'status', 'check'));
/* if ($check) { */
/* $old_status = $this->field('status'); */
/* $this->pr(17, compact('old_status')); */
/* if ($this->statusValue($old_status) < $this->occupiedEnumValue() && */
/* $this->statusValue($status) >= $this->occupiedEnumValue()) */
/* { */
/* die("Can't transition a unit from vacant to occupied"); */
/* return $this->prReturn(false); */
/* } */
/* if ($this->statusValue($old_status) >= $this->occupiedEnumValue() && */
/* $this->statusValue($status) < $this->occupiedEnumValue()) */
/* { */
/* die("Can't transition a unit from occupied to vacant"); */
/* return $this->prReturn(false); */
/* } */
/* } */
if ($check) {
if (!array_key_exists($status, $this->allowedStatusSet($id)))
return $this->prReturn(false);
}
$this->id = $id;
//pr(compact('id', 'status'));
$this->saveField('status', $status);
return $this->prReturn(true);
}
/**************************************************************************
**************************************************************************
**************************************************************************

View File

@@ -164,9 +164,9 @@ echo $this->element('statement_entries', array
//'grid_setup' => array('hiddengrid' => true),
//'caption' => '<SPAN id="receipt-charges-caption"></SPAN>',
'caption' => 'Collected ' . Inflector::pluralize($account['name']),
'filter' => array('StatementEntry.type' => 'PAYMENT',
'filter' => array(//'StatementEntry.type' => 'DISBURSEMENT',
'ChargeEntry.account_id' => $account['id']),
'exclude' => array('Account', 'Charge'),
'exclude' => array('Account', 'Charge', 'Type'),
),
));

View File

@@ -82,7 +82,7 @@ echo $this->element('ledger_entries', array
"(". $current_ledger['name'] .")"),
'filter' => array('Ledger.id' => $current_ledger['id']),
'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance',
empty($account['payments']) ? 'Tender' : null),
empty($account['receipts']) ? 'Tender' : null),
'include' => array('Debit', 'Credit', 'Sub-Total'),
)));
@@ -100,7 +100,7 @@ echo $this->element('ledger_entries', array
'caption' => "Entire Ledger",
'filter' => array('Account.id' => $account['id']),
'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance',
empty($account['payments']) ? 'Tender' : null),
empty($account['receipts']) ? 'Tender' : null),
'include' => array('Debit', 'Credit', 'Sub-Total'),
)));

View File

@@ -251,7 +251,7 @@ echo($this->element
'comment' => true,
))) . "\n");
echo $form->submit('Update') . "\n";
echo $form->submit(isset($this->data['Customer']) ? 'Update' : 'Add New Customer') . "\n";
?>
<div CLASS="dynamic-set">
@@ -268,7 +268,7 @@ echo $form->submit('Update') . "\n";
<?php
; // Alignment
echo $form->submit('Update') . "\n";
echo $form->submit(isset($this->data['Customer']) ? 'Update' : 'Add New Customer') . "\n";
echo $form->submit('Cancel', array('name' => 'cancel')) . "\n";
echo $form->end() . "\n";
echo '</div>' . "\n";

View File

@@ -70,6 +70,8 @@ function showResponse(responseText, statusText) {
if (statusText == 'success') {
// get a clean slate
//resetForm();
// REVISIT <AP>: 20090806 Add to resetForm()
updateCharges($("#customer-id").val());
}
else {
alert('not successful??');
@@ -109,8 +111,6 @@ function onRowSelect(grid_id, customer_id) {
$(grid_id).getCell(customer_id, 'Customer-id') +
'</A>');
$("#receipt-customer-name").html($(grid_id).getCell(customer_id, 'Customer-name'));
$("#receipt-balance").html("Calculating...");
$("#receipt-charges-caption").html("Please Wait...");
// Hide the "no customer" message and show the current customer
$(".customer-selection-invalid").hide();
@@ -143,7 +143,8 @@ function addPaymentSource(flash) {
addDiv('payment-entry-id', 'payment', 'payments', flash,
// HTML section
'<FIELDSET CLASS="payment subset">' +
'<LEGEND>Payment #%{id} (%{remove})</LEGEND>' +
<?php /* '<LEGEND>Payment #%{id} (%{remove})</LEGEND>' + */ ?>
'<LEGEND>Payment</LEGEND>' +
'<DIV ID="payment-div-%{id}">' +
<?php
@@ -226,7 +227,10 @@ function switchPaymentType(paymentid_base, paymentid, radioid) {
function updateChargesGrid(idlist) {
$('#charge-entries-jqGrid').setPostDataItem('idlist', serialize(idlist));
var dynamic_post = new Array();
dynamic_post['idlist'] = idlist;
$('#charge-entries-jqGrid').setPostDataItem('dynamic_post_replace', serialize(dynamic_post));
$('#charge-entries-jqGrid')
.setGridParam({ page: 1 })
.trigger("reloadGrid");
@@ -238,6 +242,8 @@ function updateCharges(id) {
url += '/'+id;
$('#charge-entries-jqGrid').clearGridData();
$("#receipt-balance").html("Calculating...");
$("#receipt-charges-caption").html("Please Wait...");
$.ajax({
type: "GET",
@@ -251,7 +257,13 @@ function updateCharges(id) {
$('#receipt-balance').html(fmtCurrency($('entries',xml).attr('balance')));
$("#receipt-charges-caption").html("Outstanding Charges");
updateChargesGrid(ids);
}
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
/* alert('ERROR'); */
/* $('#debug').html('<P>request<BR>'+escape(XMLHttpRequest)); */
/* $('#debug').append('<P>status<BR>'+escape(textStatus)); */
/* $('#debug').append('<P>error<BR>'+escape(errorThrown)); */
}
});
}
@@ -354,14 +366,18 @@ echo $this->element('form_table',
echo $form->submit('Generate Receipt') . "\n";
?>
<?php /*
<fieldset CLASS="payment superset">
<legend>Payments</legend>
*/ ?>
<input type="hidden" id="payment-entry-id" value="0">
<div id="payments"></div>
<?php /*
<fieldset> <legend>
<a href="#" onClick="addPaymentSource(true); return false;">Add Another Payment</a>
</legend> </fieldset>
</fieldset>
*/ ?>
<?php echo $form->end('Generate Receipt'); ?>

View File

@@ -84,6 +84,8 @@ echo $this->element('statement_entries', array
'filter' => array('Customer.id' => $customer['Customer']['id'],
'type !=' => 'VOID'),
'exclude' => array('Customer'),
'sort_column' => 'Effective',
'sort_order' => 'DESC',
)));
@@ -109,6 +111,8 @@ echo $this->element('ledger_entries', array
'filter' => array('Customer.id' => $customer['Customer']['id'],
'Account.id !=' => '-AR-'),
'exclude' => array('Customer'),
'sort_column' => 'Date',
'sort_order' => 'DESC',
)));

View File

@@ -7,7 +7,7 @@ $cols['Relationship'] = array('index' => 'ContactsCustomer.type', 'formatt
$cols['Name'] = array('index' => 'Customer.name', 'formatter' => 'longname');
$cols['Last Name'] = array('index' => 'PrimaryContact.last_name', 'formatter' => 'name');
$cols['First Name'] = array('index' => 'PrimaryContact.first_name', 'formatter' => 'name');
$cols['Leases'] = array('index' => 'lease_count', 'formatter' => 'number');
$cols['Leases'] = array('index' => 'current_lease_count', 'formatter' => 'number');
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
$cols['Comment'] = array('index' => 'Customer.comment', 'formatter' => 'comment');

View File

@@ -17,8 +17,9 @@ $cols['Unit'] = array('index' => 'Unit.name', 'formatter' =>
$cols['Comment'] = array('index' => 'StatementEntry.comment', 'formatter' => 'comment', 'width'=>150);
$cols['Charge'] = array('index' => 'charge', 'formatter' => 'currency');
$cols['Payment'] = array('index' => 'payment', 'formatter' => 'currency');
$cols['Type'] = array('index' => 'StatementEntry.type', 'formatter' => 'enum', 'width'=>120);
$cols['Debit'] = array('index' => 'charge', 'formatter' => 'currency');
$cols['Credit'] = array('index' => 'disbursement', 'formatter' => 'currency');
$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency');
$cols['Sub-Total'] = array('index' => 'subtotal-balance', 'formatter' => 'currency', 'sortable' => false);

View File

@@ -19,13 +19,13 @@ class FormatHelper extends AppHelper {
true));
}
function currency($amount, $spans = false) {
function currency($amount, $spans = false, $dollar_sign = null) {
if (!isset($amount))
return '-';
//return null;
$currency = self::$number->currency($amount,
'USD',
isset($dollar_sign) ? $dollar_sign : 'USD',
$spans ? array('before'=>'', 'after'=>'') : array());
if ($spans)

View File

@@ -211,8 +211,9 @@ class GridHelper extends AppHelper {
// Incorporate all other user options
if (isset($config))
$this->jqGrid_options = array_merge_recursive($this->jqGrid_options, $config);
$this->jqGrid_options = array_merge($this->jqGrid_options, $config);
//pr(compact('config') + array('jqGrid_options' => $this->jqGrid_options));
echo $view->element('jqGrid', $this->jqGrid_options);
// Since we only have one instance of this class

View File

@@ -1,88 +0,0 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="apply-deposit input">' . "\n";
echo ('<DIV CLASS="apply-deposit grid-selection-text">' .
'Lease #' . $lease['number'] .
' / Customer #' . $customer['id'] .
': ' . $customer['name'] .
' / Unit ' . $unit['name'] .
'<DIV CLASS="supporting">' .
'<TABLE>' .
/* '<TR><TD CLASS="field">Balance:</TD><TD CLASS="value">' . */
/* FormatHelper::currency($lease['stats']['balance']) . */
/* '</TD></TR>' . */
'<TR><TD CLASS="field">Deposit:</TD><TD CLASS="value">' .
FormatHelper::currency($depositBalance) .
'</TD></TR>' .
'</TABLE>' .
'</DIV>' .
'</DIV>' . "\n");
echo $form->create(null, array('id' => 'apply-deposit-form',
'url' => array('controller' => 'leases',
'action' => 'apply_deposit')
)
);
echo $form->input("Customer.id",
array('id' => 'customer-id',
'type' => 'hidden',
'value' => $customer['id']));
echo $form->input("Lease.id",
array('id' => 'lease-id',
'type' => 'hidden',
'value' => $lease['id']));
echo $form->input("LedgerEntry.Account.id",
array('id' => 'account-id',
'type' => 'hidden',
'value' => $account['id']));
echo $this->element('form_table',
array('class' => "item receipt transaction entry",
//'with_name_after' => ':',
'field_prefix' => 'Transaction',
'fields' => array
("stamp" => array('opts' => array('type' => 'text'),
'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>',
),
"amount" => array('prefix' => 'LedgerEntry',
'opts' => array('value' => $depositBalance),
),
"comment" => array('opts' => array('size' => 50),
),
)));
echo $form->end('Utilize Deposit');
?>
<script type="text/javascript"><!--
// Reset the form
function resetForm() {
datepickerNow('TransactionStamp');
}
$(document).ready(function(){
$("#TransactionStamp")
.attr('autocomplete', 'off')
.datepicker({ constrainInput: true,
numberOfMonths: [1, 1],
showCurrentAtPos: 0,
dateFormat: 'mm/dd/yy' });
resetForm();
});
--></script>
</div>

View File

@@ -1,80 +0,0 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="bad-debt input">' . "\n";
echo ('<DIV CLASS="bad-debt grid-selection-text">' .
'Lease #' . $lease['number'] .
' / Customer #' . $customer['id'] .
': ' . $customer['name'] .
' / Unit ' . $unit['name'] .
'<DIV CLASS="supporting">' .
'<TABLE>' .
'<TR><TD CLASS="field">Balance:</TD><TD CLASS="value">'.$lease['stats']['balance'].'</TD></TR>' .
'</TABLE>' .
'</DIV>' .
'</DIV>' . "\n");
echo $form->create(null, array('id' => 'receipt-form',
'url' => array('controller' => 'transactions',
'action' => 'postReceipt')));
echo $form->input("Customer.id",
array('id' => 'customer-id',
'type' => 'hidden',
'value' => $customer['id']));
echo $form->input("Lease.id",
array('id' => 'lease-id',
'type' => 'hidden',
'value' => $lease['id']));
echo $form->input("LedgerEntry.0.account_id",
array('id' => 'account-id',
'type' => 'hidden',
'value' => $account['id']));
echo $form->input("LedgerEntry.0.amount",
array('id' => 'amount',
'type' => 'hidden',
'value' => $lease['stats']['balance']));
echo $this->element('form_table',
array('class' => "item receipt transaction entry",
//'with_name_after' => ':',
'field_prefix' => 'Transaction',
'fields' => array
("stamp" => array('opts' => array('type' => 'text'),
'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>',
),
"comment" => array('opts' => array('size' => 50),
),
)));
echo $form->end('Write Off Remaining Balance');
?>
<script type="text/javascript"><!--
// Reset the form
function resetForm() {
datepickerNow('TransactionStamp');
}
$(document).ready(function(){
$("#TransactionStamp")
.attr('autocomplete', 'off')
.datepicker({ constrainInput: true,
numberOfMonths: [1, 1],
showCurrentAtPos: 0,
dateFormat: 'mm/dd/yy' });
resetForm();
});
--></script>
</div>

View File

@@ -1,74 +1,106 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="account deposit">' . "\n";
echo '<H2>Perform Bank Deposit</H2>' . "\n";
echo '<P>Make sure to select the checkboxes below for only those types of currency (Cash, Check, etc) which you intend to actually deposit (you can see all the individual items by dropping down the list below the checkbox). Then, select the Deposit Account where you will make the deposit, and click "Perform Deposit" to close the books on the selected currency types and reset them to a zero balance. On the next page, you will be provided with a deposit slip to prepare the actual deposit.' . "\n";
echo '<div class="refund input">' . "\n";
echo '<H2>Issue Refund</H2>' . "\n";
echo '<P>Enter the amount to refund, and the account to pay it from.' . "\n";
echo '<P><BR>' . "\n";
//pr(compact('tillableAccount', 'depositableAccount'));
echo $form->create(null, array('id' => 'deposit-form',
'url' => array('controller' => 'accounts',
'action' => 'deposit')));
foreach ($tillableAccount AS $acct) {
//$acct = $acct['Account'];
echo "\n";
echo $form->input('Tillable.Ledger.'.$acct['CurrentLedger']['id'].'.checked',
array(//'label' => $acct['Account']['name'],
'type' => 'checkbox',
'checked' => true,
'value' => true,
'label' => (" I have exactly " .
FormatHelper::currency($acct['Account']['stats']['Ledger']['balance']) .
" in " . ($acct['Account']['name'] === 'Cash'
? 'Cash'
: Inflector::pluralize($acct['Account']['name'])) .
" and will be depositing it all.")
));
echo "\n";
echo $form->input('Tillable.Ledger.'.$acct['CurrentLedger']['id'].'.amount',
array('type' => 'hidden',
'value' => $acct['Account']['stats']['Ledger']['balance'],
));
echo "\n";
echo $form->input('Tillable.Ledger.'.$acct['CurrentLedger']['id'].'.account_id',
array('type' => 'hidden',
'value' => $acct['Account']['id'],
));
echo "\n";
echo $form->input('Tillable.Ledger.'.$acct['CurrentLedger']['id'].'.account_name',
array('type' => 'hidden',
'value' => $acct['Account']['name'],
));
echo "\n";
$grid_div_id = 'ledger_entries'.$acct['CurrentLedger']['id'].'-list';
echo $this->element('ledger_entries', array
(// Element configuration
'ledger_id' => $acct['CurrentLedger']['id'],
'no_account' => true,
// Grid configuration
'config' => array
(
'grid_div_id' => $grid_div_id,
'caption' => ('<A HREF="#" ONCLICK="$(\'#'.$grid_div_id.' .HeaderButton\').click();'.
' return false;">Items in '.$acct['Account']['name'].' Ledger</A>'),
'grid_setup' => array('hiddengrid' => true),
),
));
if (isset($lease)) {
$customer = $lease['Customer'];
$unit = $lease['Unit'];
}
$options = array();
foreach ($depositableAccount AS $acct) {
$options[$acct['Account']['id']] = $acct['Account']['name'];
if (isset($customer['Customer']))
$customer = $customer['Customer'];
if (isset($lease['Lease']))
$lease = $lease['Lease'];
// We're not actually using a grid to select the customer / lease
// but we could/should be, and the result would be selection-text
echo ('<DIV CLASS="refund grid-selection-text">' .
'<TABLE>' . "\n");
echo ('<TR><TD style="padding-right: 1em;">' . $customer['name'] . '</TD>' .
' <TD>' . '(Customer #' . $customer['id'] . ')' . '</TD>' .
'</TR>' . "\n");
if (isset($lease))
echo ('<TR><TD style="padding-right: 1em;">' . 'Unit ' . $unit['name'] . '</TD>' .
' <TD>' . '(Lease #' . $lease['number'] . ')' . '</TD>' .
'</TR>' . "\n");
echo ('<TR><TD style="padding-right: 1em;">Refundable Balance:</TD>' .
' <TD>' . FormatHelper::currency($balance) . '</TD>' .
'</TR>' . "\n");
echo ('</TABLE>' .
'</DIV>' . "\n");
echo $form->create(null, array('id' => 'refund-form',
'url' => array('controller' => 'transactions',
'action' => 'postRefund')));
// REVISIT <AP>: 20090805
// Add Tender information to log specifically _how_ refund was paid.
echo $this->element('form_table',
array('class' => "item refund transaction entry",
//'with_name_after' => ':',
'field_prefix' => 'Transaction',
'fields' => array
("stamp" => array('opts' =>
array('type' => 'text'),
'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>',
),
"amount" => array('prefix' => 'Entry.0',
'opts' =>
array('value' =>
FormatHelper::currency($balance, false, ''),
),
),
"account_id" => array('prefix' => 'Entry.0',
'name' => 'Account',
'opts' =>
array('options' => $refundAccounts,
'value' => $defaultAccount,
),
),
"comment" => array('opts' => array('size' => 50),
),
))) . "\n";
echo $form->input("Customer.id",
array('type' => 'hidden',
'value' => $customer['id'])) . "\n";
if (isset($lease['id']))
echo $form->input("Lease.id",
array('type' => 'hidden',
'value' => $lease['id'])) . "\n";
echo $form->end('Issue Refund');
?>
<script type="text/javascript"><!--
// Reset the form
function resetForm() {
datepickerNow('TransactionStamp');
}
echo $form->input('Deposit.Account.id', array('label' => 'Deposit Account ',
'options' => $options));
echo $form->end('Perform Deposit');
$(document).ready(function(){
$("#TransactionStamp")
.attr('autocomplete', 'off')
.datepicker({ constrainInput: true,
numberOfMonths: [1, 1],
showCurrentAtPos: 0,
dateFormat: 'mm/dd/yy' });
/* End page div */
echo '</div>' . "\n";
resetForm();
});
--></script>
</div>

View File

@@ -88,6 +88,8 @@ echo $this->element('statement_entries', array
'filter' => array('Lease.id' => $lease['id']),
'include' => array('Through'),
'exclude' => array('Customer', 'Lease', 'Unit'),
'sort_column' => 'Effective',
'sort_order' => 'DESC',
)));

View File

@@ -72,7 +72,7 @@ echo $this->element('ledger_entries', array
'filter' => array('Ledger.id' => $ledger['id']),
'exclude' => array('Ledger', 'Account',
'Amount', 'Cr/Dr', 'Balance',
empty($account['payments']) ? 'Tender' : null),
empty($account['receipts']) ? 'Tender' : null),
'include' => array('Debit', 'Credit', 'Sub-Total'),
)));

View File

@@ -15,15 +15,18 @@ $customer = $entry['Customer'];
$lease = $entry['Lease'];
$entry = $entry['StatementEntry'];
$Ttype = ucfirst(strtolower($transaction['type']));
$rows = array();
$rows[] = array('ID', $entry['id']);
$rows[] = array('Transaction', $html->link('#'.$transaction['id'],
$rows[] = array($Ttype, $html->link('#'.$transaction['id'],
array('controller' => 'transactions',
'action' => 'view',
$transaction['id'])));
$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp']));
$rows[] = array('Effective', FormatHelper::date($entry['effective_date']));
$rows[] = array('Through', FormatHelper::date($entry['through_date']));
if (in_array($entry['type'], array('CHARGE', 'PAYMENT')))
$rows[] = array('Through', FormatHelper::date($entry['through_date']));
$rows[] = array('Type', $entry['type']);
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
$rows[] = array('Account', $html->link($account['name'],
@@ -56,12 +59,12 @@ echo $this->element('table',
*/
if (strtoupper($entry['type']) === 'CHARGE') {
$applied_caption = "Payments Applied";
$applied_caption = "Disbursements Applied";
//$remaining_caption = "Charge Balance";
}
else {
$applied_caption = "Applied to Charges";
//$remaining_caption = "Payment Balance";
$applied_caption = "Disbursed to Charges";
//$remaining_caption = "Disbursement Balance";
}
$remaining_caption = "Remaining Balance";

View File

@@ -0,0 +1,89 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="bad-debt input">' . "\n";
if (isset($lease)) {
$customer = $lease['Customer'];
$unit = $lease['Unit'];
}
if (isset($customer['Customer']))
$customer = $customer['Customer'];
if (isset($lease['Lease']))
$lease = $lease['Lease'];
// We're not actually using a grid to select the customer / lease
// but we could/should be, and the result would be selection-text
echo ('<DIV CLASS="bad-debt grid-selection-text">' .
'<TABLE>' . "\n");
echo ('<TR><TD style="padding-right: 1em;">' . $customer['name'] . '</TD>' .
' <TD>' . '(Customer #' . $customer['id'] . ')' . '</TD>' .
'</TR>' . "\n");
if (isset($lease))
echo ('<TR><TD style="padding-right: 1em;">' . 'Unit ' . $unit['name'] . '</TD>' .
' <TD>' . '(Lease #' . $lease['number'] . ')' . '</TD>' .
'</TR>' . "\n");
echo ('<TR><TD style="padding-right: 1em;">Remaining Balance:</TD>' .
' <TD>' . FormatHelper::currency($balance) . '</TD>' .
'</TR>' . "\n");
echo ('</TABLE>' .
'</DIV>' . "\n");
echo $form->create(null, array('id' => 'receipt-form',
'url' => array('controller' => 'transactions',
'action' => 'postWriteOff'))) . "\n";
echo $form->input("Customer.id",
array('type' => 'hidden',
'value' => $customer['id'])) . "\n";
if (isset($lease['id']))
echo $form->input("Lease.id",
array('type' => 'hidden',
'value' => $lease['id'])) . "\n";
echo $form->input("Entry.0.amount",
array('type' => 'hidden',
'value' => $balance)) . "\n";
echo $this->element('form_table',
array('class' => "item receipt transaction entry",
//'with_name_after' => ':',
'field_prefix' => 'Transaction',
'fields' => array
("stamp" => array('opts' => array('type' => 'text'),
'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>',
),
"comment" => array('opts' => array('size' => 50),
),
))) . "\n";
echo $form->end('Write Off Remaining Balance');
?>
<script type="text/javascript"><!--
// Reset the form
function resetForm() {
datepickerNow('TransactionStamp');
}
$(document).ready(function(){
$("#TransactionStamp")
.attr('autocomplete', 'off')
.datepicker({ constrainInput: true,
numberOfMonths: [1, 1],
showCurrentAtPos: 0,
dateFormat: 'mm/dd/yy' });
resetForm();
});
--></script>
</div>

View File

@@ -67,7 +67,11 @@ echo '<div CLASS="detail supporting">' . "\n";
* Statement Entries
*/
if ($transaction['type'] === 'INVOICE' || $transaction['type'] === 'RECEIPT') {
if ($transaction['type'] === 'INVOICE' ||
$transaction['type'] === 'RECEIPT' ||
$transaction['type'] === 'CREDIT_NOTE' ||
$transaction['type'] === 'PAYMENT'
) {
echo $this->element('statement_entries', array
(// Grid configuration
'config' => array

27
site/views/units/edit.ctp Normal file
View File

@@ -0,0 +1,27 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="unit edit">' . "\n";
echo $form->create('Unit', array('action' => 'edit')) . "\n";
echo $form->input('id') . "\n";
echo($this->element
('form_table',
array('class' => 'item unit detail',
'caption' => isset($this->data['Unit']) ? 'Edit Unit' : 'New Unit',
'fields' => array
('name' => true,
'unit_size_id' => true,
'status' => array('opts' =>
array('options' => $statusEnums,
),
),
'deposit' => true,
'rent' => true,
'comment' => true,
))) . "\n");
echo $form->submit('Update') . "\n";
echo $form->submit('Cancel', array('name' => 'cancel')) . "\n";
echo $form->end() . "\n";
echo '</div>' . "\n";

View File

@@ -18,6 +18,9 @@ security deposits charged.
Customer Selection on the Receipt Page is broken.
(Selecting a row and waiting for the update).
Allow waiving a complete charge, even if it already has payments
applied (at the moment, we just can waive the charge balance).
Get Petty Cash working. We'll need to add one or more expense
accounts. We'll also need to implement purchase order
functionality, or at least simple an expense page.