This rework is nowhere near complete, but there are certain things that are falling in place, and worth capturing. I started a branch for just this purpose of being able to check in intermediate work.

git-svn-id: file:///svn-source/pmgr/branches/yafr_20090716@352 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
abijah
2009-07-19 23:35:25 +00:00
parent e303898a95
commit 6ac0204baf
26 changed files with 3058 additions and 1767 deletions

View File

@@ -869,8 +869,6 @@ INSERT INTO `pmgr_accounts` (`type`, `name`, `level`)
INSERT INTO `pmgr_accounts` (`type`, `name`) INSERT INTO `pmgr_accounts` (`type`, `name`)
VALUES VALUES
('ASSET', 'A/R' ), ('ASSET', 'A/R' ),
('ASSET', 'Invoice' ),
('ASSET', 'Receipt' ),
-- REVISIT <AP>: 20090710 : We don't really need NSF, as it -- REVISIT <AP>: 20090710 : We don't really need NSF, as it
-- will always run a zero balance. However, it will help -- will always run a zero balance. However, it will help
-- us identify how serious the NSF situation is. -- us identify how serious the NSF situation is.
@@ -896,9 +894,9 @@ INSERT INTO `pmgr_accounts` (`type`, `name`, `chargeable`, `trackable`)
('INCOME', 'Late Charge', 1, 0), ('INCOME', 'Late Charge', 1, 0),
('INCOME', 'NSF Charge', 1, 0), ('INCOME', 'NSF Charge', 1, 0),
('INCOME', 'Damage', 1, 0); ('INCOME', 'Damage', 1, 0);
INSERT INTO `pmgr_accounts` (`type`, `name`, `depositable`, `trackable`) INSERT INTO `pmgr_accounts` (`type`, `name`, `depositable`, `refundable`, `trackable`)
VALUES VALUES
('ASSET', 'Bank', 1, 0); ('ASSET', 'Bank', 1, 1, 0);
INSERT INTO `pmgr_accounts` (`type`, `name`, `trackable`) INSERT INTO `pmgr_accounts` (`type`, `name`, `trackable`)
VALUES VALUES
('EXPENSE', 'Bad Debt', 0), ('EXPENSE', 'Bad Debt', 0),
@@ -970,89 +968,139 @@ DROP TABLE IF EXISTS `pmgr_transactions`;
CREATE TABLE `pmgr_transactions` ( CREATE TABLE `pmgr_transactions` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `type` ENUM('INVOICE', `type` ENUM('INVOICE',
-- 'RECEIPT') 'CREDIT',
-- NOT NULL, 'RECEIPT',
'REFUND',
'TRANSFER')
NOT NULL,
`stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- amount is for convenience. It can always be calculated from
-- the associated double entries (and therefore will need to be
-- updated if they should change in any way).
`amount` FLOAT(12,2) DEFAULT NULL,
-- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- `lease_id` INT(10) UNSIGNED DEFAULT NULL,
`comment` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- -- ----------------------------------------------------------------------
-- -- -- ----------------------------------------------------------------------
-- -- -- TABLE pmgr_charge_details
-- DROP TABLE IF EXISTS `pmgr_charge_details`;
-- CREATE TABLE `pmgr_charge_details` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- -- Through date is used if/when a charge covers a certain time period,
-- -- like rent. A security deposit, for example, would not use the
-- -- through date.
-- `through_date` DATE DEFAULT NULL, -- last day
-- `due_date` DATE DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- -- ----------------------------------------------------------------------
-- -- -- ----------------------------------------------------------------------
-- -- -- TABLE pmgr_payment_details
-- DROP TABLE IF EXISTS `pmgr_payment_details`;
-- CREATE TABLE `pmgr_payment_details` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- -- name may (or may not) be used to clarify in reports
-- -- for example, 'Check #1234' as the payment name.
-- `name` VARCHAR(80) DEFAULT NULL,
-- -- REVISIT <AP>: 20090716
-- -- Type may needs to be another table, so that the user
-- -- can add new types. If so, they will also have to
-- -- specify the required data1/2/3/4 fields.
-- -- For now, this will work.
-- `monetary_type` ENUM('CASH',
-- 'CHECK',
-- 'MONEYORDER',
-- 'ACH',
-- 'DEBITCARD',
-- 'CREDITCARD')
-- DEFAULT NULL,
-- -- REVISIT <AP>: 20090605
-- -- Check Number;
-- -- Routing Number, Account Number;
-- -- Card Number, Expiration Date; CVV2 Code
-- -- etc.
-- -- REVISIT <AP> 20090630
-- -- I _think_ that CVV2 is NEVER supposed to
-- -- be stored ANYWHERE. Merchants agree to
-- -- use it only to verify the transaction and
-- -- then leave no record of it, so that even
-- -- if their security is compromised, no one
-- -- will know the CVV2 code unless they are
-- -- in physical possession of the card.
-- `data1` VARCHAR(80) DEFAULT NULL,
-- `data2` VARCHAR(80) DEFAULT NULL,
-- `data3` VARCHAR(80) DEFAULT NULL,
-- `data4` VARCHAR(80) DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_entries
DROP TABLE IF EXISTS `pmgr_entries`;
CREATE TABLE `pmgr_entries` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`type` ENUM('CHARGE',
'PAYMENT',
'TRANSFER')
NOT NULL,
-- The account of the entry
`account_id` INT(10) UNSIGNED DEFAULT NULL,
`crdr` ENUM('DEBIT',
'CREDIT')
NOT NULL,
-- The actual ledgers where this entry is recorded
`double_entry_id` INT(10) UNSIGNED DEFAULT NULL,
-- Through date is used if/when a charge covers a certain time period,
-- like rent. A security deposit, for example, would not use the
-- through date.
`through_date` DATE DEFAULT NULL, -- last day
`due_date` DATE DEFAULT NULL, `due_date` DATE DEFAULT NULL,
-- REVISIT <AP>: 20090604 -- name may (or may not) be used to clarify in reports
-- How should we track which charges have been paid? -- for example, 'Check #1234' as the payment name.
-- `related_transaction_id` INT(10) UNSIGNED NOT NULL,
-- `related_entry_id` INT(10) UNSIGNED NOT NULL,
`comment` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_ledger_entries
DROP TABLE IF EXISTS `pmgr_ledger_entries`;
CREATE TABLE `pmgr_ledger_entries` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Effective date may be used for a variety of entries
-- charges & payments are not always effective at the
-- time of transaction. Through date, on the other hand,
-- will probably only be relevant for charges, such as
-- rent, which is effective for a range of dates.
`effective_date` DATE DEFAULT NULL, -- first day
`through_date` DATE DEFAULT NULL, -- last day
`monetary_source_id` INT(10) UNSIGNED DEFAULT NULL, -- NULL if internal transfer
`transaction_id` INT(10) UNSIGNED NOT NULL,
`customer_id` INT(10) UNSIGNED DEFAULT NULL,
`lease_id` INT(10) UNSIGNED DEFAULT NULL,
`amount` FLOAT(12,2) NOT NULL,
-- REVISIT <AP>: 20090707
-- Experimental. Considering automatically hooking
-- charges to their invoice. This may help ease the
-- ongoing accounting dilema that we've been having.
-- It might allow us to keep the underlying invoice
-- ledgers without having to expose the user to them.
`root_transaction_id` INT(10) UNSIGNED DEFAULT NULL,
`debit_ledger_id` INT(10) UNSIGNED NOT NULL,
`credit_ledger_id` INT(10) UNSIGNED NOT NULL,
`comment` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_reconciliations
DROP TABLE IF EXISTS `pmgr_reconciliations`;
CREATE TABLE `pmgr_reconciliations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`debit_ledger_entry_id` INT(10) UNSIGNED NOT NULL,
`credit_ledger_entry_id` INT(10) UNSIGNED NOT NULL,
`amount` FLOAT(12,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_monetary_sources
DROP TABLE IF EXISTS `pmgr_monetary_sources`;
CREATE TABLE `pmgr_monetary_sources` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(80) DEFAULT NULL, `name` VARCHAR(80) DEFAULT NULL,
-- REVISIT <AP>: 20090716
-- Type may needs to be another table, so that the user
-- can add new types. If so, they will also have to
-- specify the required data1/2/3/4 fields.
-- For now, this will work.
`monetary_type` ENUM('CASH',
'CHECK',
'MONEYORDER',
'ACH',
'DEBITCARD',
'CREDITCARD')
DEFAULT NULL,
-- REVISIT <AP>: 20090605 -- REVISIT <AP>: 20090605
-- Check Number; -- Check Number;
-- Routing Number, Account Number; -- Routing Number, Account Number;
@@ -1077,6 +1125,237 @@ CREATE TABLE `pmgr_monetary_sources` (
) ENGINE=MyISAM DEFAULT CHARSET=utf8; ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_reconciliations
DROP TABLE IF EXISTS `pmgr_reconciliations`;
CREATE TABLE `pmgr_reconciliations` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`debit_entry_id` INT(10) UNSIGNED NOT NULL,
`credit_entry_id` INT(10) UNSIGNED NOT NULL,
`amount` FLOAT(12,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_double_entries
DROP TABLE IF EXISTS `pmgr_double_entries`;
CREATE TABLE `pmgr_double_entries` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`transaction_id` INT(10) UNSIGNED DEFAULT NULL,
-- Effective date is when the charge/payment/transfer actually
-- takes effect (since it may not be at the time of the transaction).
`effective_date` DATE DEFAULT NULL, -- first day
`customer_id` INT(10) UNSIGNED DEFAULT NULL,
`lease_id` INT(10) UNSIGNED DEFAULT NULL,
`debit_ledger_id` INT(10) UNSIGNED NOT NULL,
`credit_ledger_id` INT(10) UNSIGNED NOT NULL,
`amount` FLOAT(12,2) NOT NULL,
`comment` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ----------------------------------------------------------------------
-- ----------------------------------------------------------------------
-- TABLE pmgr_charges_payments
DROP TABLE IF EXISTS `pmgr_charges_payments`;
CREATE TABLE `pmgr_charges_payments` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- Mark which charge the payment reconciles, and how much was applied
`charge_entry_id` INT(10) UNSIGNED NOT NULL,
`payment_entry_id` INT(10) UNSIGNED NOT NULL,
`amount` FLOAT(12,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_folders
-- --
-- -- It's a dumb name, but at the moment, it's how I'm thinking about
-- -- this all. In the generic, a purchase begins with a purchase order
-- -- from the customer. When you receive it, you grab a new manilla
-- -- folder, drop the purchase order in, and start making the widgets.
-- -- when their ready, you ship them with invoice, and drop a copy of
-- -- the invoice in the folder. When they pay, you generate a receipt,
-- -- and drop a copy in the folder. If the check bounces, you note
-- -- that in the folder, and generate a new NSF fee invoice (which gets
-- -- put into its OWN new folder). When they pay again, you generate
-- -- another receipt and put it into the folder, and this process could
-- -- repeat until the invoice finally gets paid, or you write it off as
-- -- a bad debt, which would be noted, of course, in the folder.
-- DROP TABLE IF EXISTS `pmgr_folders`;
-- CREATE TABLE `pmgr_folders` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- `comment` VARCHAR(255) DEFAULT NULL,
-- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- `lease_id` INT(10) UNSIGNED DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_invoices
-- DROP TABLE IF EXISTS `pmgr_invoices`;
-- CREATE TABLE `pmgr_invoices` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- -- amount is for convenience. It can always be calculated from
-- -- the attached ledger entries (and therefore will need to be
-- -- updated if they should change in any way).
-- `amount` FLOAT(12,2) DEFAULT NULL,
-- -- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- -- `lease_id` INT(10) UNSIGNED DEFAULT NULL,
-- -- `folder_id` INT(10) UNSIGNED NOT NULL,
-- `comment` VARCHAR(255) DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_charges
-- DROP TABLE IF EXISTS `pmgr_charges`;
-- CREATE TABLE `pmgr_charges` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- -- Effective date is when the charge actually takes effect
-- -- (since it may not be at the time of the invoice). Through
-- -- date is used if/when a charge covers a certain time period,
-- -- like rent. A security deposit, for example, would not use
-- -- the through date.
-- `effective_date` DATE DEFAULT NULL, -- first day
-- `through_date` DATE DEFAULT NULL, -- last day
-- `due_date` DATE DEFAULT NULL,
-- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- `lease_id` INT(10) UNSIGNED DEFAULT NULL,
-- `amount` FLOAT(12,2) DEFAULT NULL,
-- `comment` VARCHAR(255) DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_payments
-- DROP TABLE IF EXISTS `pmgr_payments`;
-- CREATE TABLE `pmgr_payments` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- -- Customer ID ensures that they payment doesn't get lost
-- -- in the system if either a) it's a pre-payment, or
-- -- b) the associated invoice is deleted.
-- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- `amount` FLOAT(12,2) DEFAULT NULL,
-- -- name may (or may not) be used to clarify in reports
-- -- for example, 'Check #1234' as the payment name.
-- `name` VARCHAR(80) DEFAULT NULL,
-- -- REVISIT <AP>: 20090716
-- -- Type may needs to be another table, so that the user
-- -- can add new types. If so, they will also have to
-- -- specify the required data1/2/3/4 fields.
-- -- For now, this will work.
-- `type` ENUM('CASH',
-- 'CHECK',
-- 'MONEYORDER',
-- 'ACH',
-- 'DEBITCARD',
-- 'CREDITCARD')
-- DEFAULT NULL,
-- -- REVISIT <AP>: 20090605
-- -- Check Number;
-- -- Routing Number, Account Number;
-- -- Card Number, Expiration Date; CVV2 Code
-- -- etc.
-- -- REVISIT <AP> 20090630
-- -- I _think_ that CVV2 is NEVER supposed to
-- -- be stored ANYWHERE. Merchants agree to
-- -- use it only to verify the transaction and
-- -- then leave no record of it, so that even
-- -- if their security is compromised, no one
-- -- will know the CVV2 code unless they are
-- -- in physical possession of the card.
-- `data1` VARCHAR(80) DEFAULT NULL,
-- `data2` VARCHAR(80) DEFAULT NULL,
-- `data3` VARCHAR(80) DEFAULT NULL,
-- `data4` VARCHAR(80) DEFAULT NULL,
-- `comment` VARCHAR(255) DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- -- ----------------------------------------------------------------------
-- -- ----------------------------------------------------------------------
-- -- TABLE pmgr_transfers
-- DROP TABLE IF EXISTS `pmgr_transfers`;
-- CREATE TABLE `pmgr_transfers` (
-- `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- -- Effective date is when the charge actually takes effect
-- -- (since it may not be at the time of the invoice). Through
-- -- date is used if/when a charge covers a certain time period,
-- -- like rent. A security deposit, for example, would not use
-- -- the through date.
-- `effective_date` DATE DEFAULT NULL, -- first day
-- `through_date` DATE DEFAULT NULL, -- last day
-- `due_date` DATE DEFAULT NULL,
-- `customer_id` INT(10) UNSIGNED DEFAULT NULL,
-- `lease_id` INT(10) UNSIGNED DEFAULT NULL,
-- `amount` FLOAT(12,2) DEFAULT NULL,
-- `comment` VARCHAR(255) DEFAULT NULL,
-- PRIMARY KEY (`id`)
-- ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- ###################################################################### -- ######################################################################
-- ###################################################################### -- ######################################################################

View File

@@ -549,22 +549,6 @@ $newdb{'lookup'}{'payment_type'}{12}
= { 'name' => 'Concession', 'account_name' => 'Concession' }; = { 'name' => 'Concession', 'account_name' => 'Concession' };
$newdb{'ids'}{'monetary_source'} = {};
$newdb{'ids'}{'monetary_source'}{'internal'} = undef;
addRow('monetary_sources',
{ 'name' => 'Cash',
'comment' => 'Monetary source used for any cash transaction' });
$newdb{'ids'}{'monetary_source'}{'Cash'} =
$newdb{'tables'}{'monetary_sources'}{'autoid'};
addRow('monetary_sources',
{ 'name' => 'Closing',
'comment' => 'Credited at the closing table' });
$newdb{'ids'}{'monetary_source'}{'Closing'} =
$newdb{'tables'}{'monetary_sources'}{'autoid'};
###################################################################### ######################################################################
@@ -895,7 +879,9 @@ foreach $row (@{query($sdbh, $query)}) {
'movein_date' => datefmt($row->{'DateIn'}), 'movein_date' => datefmt($row->{'DateIn'}),
'moveout_date' => datefmt($row->{'DateOut'}), 'moveout_date' => datefmt($row->{'DateOut'}),
'close_date' => datefmt($row->{'DateClosed'}), 'close_date' => datefmt($row->{'DateClosed'}),
'rent' => $row->{'Rent'} }); 'rent' => $row->{'Rent'},
'comment' => "LedgerID: $row->{'LedgerID'}",
});
$newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'} = $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'} =
$newdb{'tables'}{'leases'}{'autoid'}; $newdb{'tables'}{'leases'}{'autoid'};
@@ -910,7 +896,7 @@ foreach $row (@{query($sdbh, $query)}) {
###################################################################### ######################################################################
###################################################################### ######################################################################
## ##
## TRANSACTIONS ## INVOICES
## ##
###################################################################### ######################################################################
@@ -926,17 +912,16 @@ foreach $row (@{query($sdbh, $query)}) {
$row->{'ChargeDescription'}, $row->{'LedgerID'}); $row->{'ChargeDescription'}, $row->{'LedgerID'});
addRow('transactions', addRow('transactions',
{ 'stamp' => $stamp, { 'type' => 'INVOICE',
'stamp' => $stamp,
#'amount' => $row->{'InvoiceAmount'},
#'comment' => "Invoice Transaction", #'comment' => "Invoice Transaction",
}); });
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}
= { 'invoice' => = { 'invoice' => {'id' => $newdb{'tables'}{'transactions'}{'autoid'},
{ 'tx' => $newdb{'tables'}{'transactions'}{'autoid'}, 'amount' => $row->{'InvoiceAmount'} },
'lease_id' => $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'}, };
'customer_id' => $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'},
'amount' => $row->{'InvoiceAmount'},
} };
# Charges are the only way we have to figure out security # Charges are the only way we have to figure out security
# deposit requirements for a lease. So, if we encounter # deposit requirements for a lease. So, if we encounter
@@ -947,51 +932,6 @@ foreach $row (@{query($sdbh, $query)}) {
]{'deposit'} = $row->{'ChargeAmount'}; ]{'deposit'} = $row->{'ChargeAmount'};
} }
# Invoice must debit the A/R ledger...
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'debit_ledger_id'}
= $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
if ($use_invoice) {
addRow('transactions',
{ 'stamp' => $stamp,
#'comment' => "Charges Transaction",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'}
= $newdb{'tables'}{'transactions'}{'autoid'};
# ...and credit the Invoice ledger.
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'credit_ledger_id'}
= $newdb{'lookup'}{'account'}{'Invoice'}{'ledger_id'};
# Create the invoice entry
# debit: A/R credit: Invoice
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'tx'},
'root_transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'credit_ledger_id'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'customer_id'},
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'lease_id'},
'amount' => $row->{'InvoiceAmount'},
#'comment' => "Invoice: Charge: $row->{'ChargeID'}",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'};
}
else {
# OK, no invoice ledger
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'tx'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'credit_ledger_id'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'debit_ledger_id'};
}
} }
@@ -1001,97 +941,100 @@ foreach $row (@{query($sdbh, $query)}) {
$query = "SELECT * FROM Charges ORDER BY ChargeID"; $query = "SELECT * FROM Charges ORDER BY ChargeID";
foreach $row (@{query($sdbh, $query)}) { foreach $row (@{query($sdbh, $query)}) {
my (undef, $effective_date, $through_date) = my (undef, $effective_date, $through_date) =
dates('charge', $row->{'ChargeDate'}, $row->{'EndDate'}, dates('charge', $row->{'ChargeDate'}, $row->{'EndDate'},
$row->{'ChargeDescription'}, $row->{'LedgerID'}); $row->{'ChargeDescription'}, $row->{'LedgerID'});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tx'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'}; = $row->{'ChargeAmount'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'}; = $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'},
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'}; = $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'};
# Charge must credit the Charge ledger... # Charge must credit the Charge ledger...
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_account_id'}
= $newdb{'lookup'}{'charge_type'}{$row->{'ChargeDescription'}}{'account_id'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'}
= $newdb{'lookup'}{'charge_type'}{$row->{'ChargeDescription'}}{'ledger_id'}; = $newdb{'lookup'}{'charge_type'}{$row->{'ChargeDescription'}}{'ledger_id'};
# ...and debit the Invoice ledger. # ...and debit the A/R ledger.
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_account_id'}
= $newdb{'lookup'}{'account'}{'A/R'}{'account_id'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'credit_ledger_id'}; = $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
# Add the charge entry # debit: A/R credit: Rent/LateCharge/Etc
# debit: Invoice credit: Rent/LateCharge/Etc addRow('double_entries',
addRow('ledger_entries', { 'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'id'},
{ 'effective_date' => $effective_date, 'effective_date' => $effective_date,
'through_date' => $through_date, 'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'}, 'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tx'}, 'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'}, 'credit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'}, 'amount' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}, 'comment' => "Double Entry: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}",
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}, });
'amount' => $row->{'ChargeAmount'}, $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'double_entry_id'}
#'comment' => "Charge: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}", = $newdb{'tables'}{'double_entries'}{'autoid'};
# Add the Charge Entry
addRow('entries',
{ 'type' => 'CHARGE',
'through_date' => $through_date,
'double_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'double_entry_id'},
'account_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_account_id'},
'crdr' => 'CREDIT',
'comment' => "Charge: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'entry_id'}
= $newdb{'tables'}{'entries'}{'autoid'};
# Add the A/R entry
addRow('entries',
{ 'type' => 'TRANSFER',
'double_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'double_entry_id'},
'account_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_account_id'},
'crdr' => 'DEBIT',
'comment' => "Charge A/R: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}",
}); });
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ledger_entry_id'} $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ar_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'}; = $newdb{'tables'}{'entries'}{'autoid'};
if ($use_invoice) { $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'}
# Reconcile the Invoice account. Our two entries look like: = $row->{'ChargeAmount'};
# debit: Invoice credit: Rent/LateCharge/Etc
# debit: A/R credit: Invoice
# Since this is from the perspective of the Invoice account,
# the credit entry is the Invoice<->A/R, and the debit
# entry is the actual charge ledger entry.
addRow('reconciliations',
{ 'debit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ledger_entry_id'},
'credit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'},
'amount' => $row->{'ChargeAmount'},
});
}
else {
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ledger_entry_id'};
}
next unless $row->{'TaxAmount'}; next unless $row->{'TaxAmount'};
# Add the tax charge entry # # Add the tax charge entry
# debit: Invoice credit: Tax # # debit: Invoice credit: Tax
addRow('ledger_entries', # addRow('ledger_entries',
{ 'effective_date' => $effective_date, # { 'effective_date' => $effective_date,
'through_date' => $through_date, # 'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'}, # 'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'id'},
'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tx'}, # 'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'}, # 'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Tax'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Tax'}{'ledger_id'}, # 'amount' => $row->{'TaxAmount'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}, # #'comment' => "Tax for ChargeID:$row->{'ChargeID'}",
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}, # });
'amount' => $row->{'TaxAmount'},
#'comment' => "Tax for ChargeID:$row->{'ChargeID'}",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tax_ledger_entry_id'} # $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tax_ledger_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'}; # = $newdb{'tables'}{'ledger_entries'}{'autoid'};
if ($use_invoice) {
# Reconcile the Invoice account. Our two entries look like:
# debit: Invoice credit: Tax
# debit: A/R credit: Invoice
# Since this is from the perspective of the Invoice account,
# the credit entry is the Invoice<->A/R, and the debit
# entry is the actual tax ledger entry.
addRow('reconciliations',
{ 'debit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tax_ledger_entry_id'},
'credit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'},
'amount' => $row->{'TaxAmount'},
});
}
} }
######################################################################
######################################################################
######################################################################
######################################################################
######################################################################
######################################################################
##
## RECEIPTS
##
###################################################################### ######################################################################
## Receipts ## Receipts
@@ -1186,190 +1129,188 @@ $query =
" WHERE P.ReceiptNum = R.ReceiptNum" . " WHERE P.ReceiptNum = R.ReceiptNum" .
" GROUP BY R.ReceiptNum, R.ReceiptDate, P.PaymentType, P.CheckNum" . " GROUP BY R.ReceiptNum, R.ReceiptDate, P.PaymentType, P.CheckNum" .
" ORDER BY R.ReceiptNum, P.PaymentType"; " ORDER BY R.ReceiptNum, P.PaymentType";
# print Dumper query($sdbh, $query);
# exit;
foreach $row (@{query($sdbh, $query)}) { foreach $row (@{query($sdbh, $query)}) {
# if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'}) { # if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'}) {
# next; # next;
# } # }
my ($stamp, $effective_date, $through_date) = my ($stamp, $effective_date, $through_date) =
dates('receipt', $row->{'ReceiptDate'}, undef); dates('receipt', $row->{'ReceiptDate'}, undef);
if (!$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}) { if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}) {
addRow('transactions', print Dumper $newdb{'lookup'}{'receipt'};
{ 'stamp' => $stamp, print Dumper $row;
#'comment' => "Receipt Transaction", die "REALLY?";
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}
= {'tx' => $newdb{'tables'}{'transactions'}{'autoid'},
'date' => $stamp,
};
addRow('transactions',
{ 'stamp' => $stamp,
#'comment' => "Payment Split Transaction",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'payment_tx'}
= $newdb{'tables'}{'transactions'}{'autoid'};
} }
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}
= { 'date' => $stamp,
'amount' => $row->{'ReceiptAmount'},
};
if ($row->{'ReceiptDate'} =~ m%3/25/2009%) { if ($row->{'ReceiptDate'} =~ m%3/25/2009%) {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'}
= $newdb{'ids'}{'monetary_source'}{'Closing'}; = 'Closing';
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
= 'Bank'; = 'Bank';
} }
else { else {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'}
= $newdb{'ids'}{'monetary_source'}{ = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'};
$newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'type'}
}; = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'account_name'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
= $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'account_name'}; = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'account_name'};
}
# Set up a monetary source for the receipt, if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'} eq 'Check') {
if (!$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}) { $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'}
my $name = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'}; = 'Check #' . $row->{'CheckNum'};
my $data1; $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'data1'}
if ($name eq 'Check') { = $row->{'CheckNum'};
$name = 'Check #' . $row->{'CheckNum'};
$data1 = $row->{'CheckNum'};
} }
addRow('monetary_sources',
{ 'name' => $name,
'data1' => $data1,
#'comment' => "Receipt:$row->{'ReceiptNum'}; Payment:$row->{'PaymentType'}; Check:$row->{'CheckNum'}",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}
= $newdb{'tables'}{'monetary_sources'}{'autoid'};
} }
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'type'} = undef
if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'type'}
== 'Concession');
addRow('transactions',
{ 'type' => 'RECEIPT',
'stamp' => $stamp,
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'receipt_id'}
= $newdb{'tables'}{'transactions'}{'autoid'};
# Receipt must debit the "money" asset (bank, cash, check, etc)... # Receipt must debit the "money" asset (bank, cash, check, etc)...
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_account_id'}
= $newdb{'lookup'}{'account'}{
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
}{'account_id'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_ledger_id'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_ledger_id'}
= $newdb{'lookup'}{'account'}{ = $newdb{'lookup'}{'account'}{
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
}{'ledger_id'}; }{'ledger_id'};
# ...and credit the Receipt ledger # ...and credit the A/R ledger
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_account_id'}
= $newdb{'lookup'}{'account'}{'A/R'}{'account_id'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_ledger_id'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_ledger_id'}
= $newdb{'lookup'}{'account'}{'Receipt'}{'ledger_id'}; = $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
# NOTE THE ABOVE LARGE COMMENT BLOCK # debit: Cash/Check/Etc credit: A/R
# The choice of credit/debit ledgers does not mirror the addRow('double_entries',
# choices for invoice. This _should_ be A/R to Receipt, { 'transaction_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'receipt_id'},
# but it is Money to Receipt instead. 'effective_date' => $effective_date,
# debit: Cash/Check/Etc credit: Receipt 'customer_id' => undef, # This is set later...
addRow('ledger_entries', 'debit_ledger_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_ledger_id'},
{ 'effective_date' => $effective_date, 'credit_ledger_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_ledger_id'},
'through_date' => $through_date, 'amount' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'amount'},
'monetary_source_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}, 'comment' => "Double Entry Receipt: $row->{'ReceiptNum'}; Type: $row->{'PaymentType'}",
'transaction_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'tx'}, });
'debit_ledger_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_ledger_id'},
'amount' => $row->{'ReceiptAmount'},
#'comment' => "Receipt: $row->{'ReceiptNum'}; ",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'} $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'}; = $newdb{'tables'}{'double_entries'}{'autoid'};
# Add the Payment Entry
addRow('entries',
{ 'type' => 'PAYMENT',
'account_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'debit_account_id'},
'crdr' => 'DEBIT',
'double_entry_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'},
'name' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'},
'monetary_type' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'type'},
'data1' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'data1'},
'comment' => "Receipt: $row->{'ReceiptNum'}; Type: $row->{'PaymentType'}",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'entry_id'}
= $newdb{'tables'}{'entries'}{'autoid'};
# Add the A/R Entry
addRow('entries',
{ 'type' => 'TRANSFER',
'account_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_account_id'},
'crdr' => 'CREDIT',
'double_entry_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'},
'comment' => "Receipt A/R: $row->{'ReceiptNum'}; Type: $row->{'PaymentType'}",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ar_entry_id'}
= $newdb{'tables'}{'entries'}{'autoid'};
} }
###################################################################### ######################################################################
## Payments ## Payments to Charges assignments
$newdb{'lookup'}{'payment'} = {}; $newdb{'lookup'}{'payment'} = {};
$query = "SELECT * FROM Payments ORDER BY PaymentID"; $query = "SELECT * FROM Payments ORDER BY PaymentID";
foreach $row (@{query($sdbh, $query)}) foreach $row (@{query($sdbh, $query)})
{ {
my (undef, $effective_date, $through_date) =
dates('payment', $row->{'PaymentDate'}, undef);
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}} $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}
= { 'tx' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'payment_tx'} }; = { 'receipt_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'receipt_id'},
'ar_entry_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ar_entry_id'},
'entry_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'entry_id'},
'amount' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'amount'},
};
# Ensure Payment has the right customer
# NOTE THE ABOVE LARGE COMMENT BLOCK $newdb{'tables'}{'double_entries'}{'rows'}[
# The choice of credit/debit ledgers does not mirror the $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'}
# choices for charges. This _should_ be Money to Receipt,
# but it is A/R to Receipt instead.
# Ensure Receipt has the right customer
$newdb{'tables'}{'ledger_entries'}{'rows'}[
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'}
]{'customer_id'} = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}; ]{'customer_id'} = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'};
# Payment must debit the associated receipt ledger (which should be Receipt)... next
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'debit_ledger_id'} if ($newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'reconciled'});
= $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'credit_ledger_id'};
# ...and credit the A/R ledger.
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'credit_ledger_id'}
= $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
# Add the payment entry
# debit: Receipt credit: A/R
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
'transaction_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'tx'},
'debit_ledger_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'credit_ledger_id'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'amount' => $row->{'PaymentAmount'},
#'comment' => "Split; Receipt: $row->{'ReceiptNum'}; Charge: $row->{'ChargeID'}; Payment: $row->{'PaymentID'}",
});
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ledger_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'};
# Reconcile the Receipt account. Our two entries look like:
# debit: Cash/Check/Etc credit: Receipt
# debit: Receipt credit: A/R
# Since this is from the perspective of the Receipt account,
# the debit entry is the Receipt<->A/R, and the credit
# entry is the actual receipt ledger entry.
addRow('reconciliations',
{ 'debit_ledger_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ledger_entry_id'},
'credit_ledger_entry_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'},
'amount' => $row->{'PaymentAmount'},
});
# Figure out how much of the charge can be reconciled # Figure out how much of the charge can be reconciled
my $reconcile_amount = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'amount'}; my $charge_amount = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'};
#print Dumper($newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}); exit; my $payment_amount = $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'amount'};
$reconcile_amount = $row->{'PaymentAmount'} if $row->{'PaymentAmount'} <= $reconcile_amount;
# Reconcile the A/R account. Our two entries look like: my $reconcile_amount = ($charge_amount < $payment_amount) ? $charge_amount : $payment_amount;
# debit: Receipt credit: A/R
# debit: A/R credit: Invoice # Reconcile the A/R Account
# Since this is from the perspective of the A/R account,
# the debit entry is the Invoice<->A/R, and the credit
# entry is the Receipt<->A/R.
addRow('reconciliations', addRow('reconciliations',
{ 'debit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'}, { 'debit_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ar_entry_id'},
'credit_ledger_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ledger_entry_id'}, 'credit_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ar_entry_id'},
'amount' => $reconcile_amount, 'amount' => $reconcile_amount,
}); });
# Reconcile the payment to the charge
addRow('charges_payments',
{ 'charge_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'entry_id'},
'payment_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'entry_id'},
'amount' => $reconcile_amount,
});
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'reconciled'} = 1;
# Update the transaction to use the memo from this payment # Update the transaction to use the memo from this payment
if ($row->{'Memo'}) { if ($row->{'Memo'}) {
my $txid = $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'tx'}; my $id = $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'entry_id'};
$newdb{'tables'}{'transactions'}{'rows'}[$txid]{'comment'} = $row->{'Memo'}; $newdb{'tables'}{'entries'}{'rows'}[$id]{'comment'} = $row->{'Memo'};
} }
} }
######################################################################
## Special cases - Fix sitelink Brenda Harmon bug
# print("Special Cases - Fix Brenda Harmon...\n");
# $query =
# "UPDATE pmgr_contacts" .
# " SET first_name = 'Krystan'" .
# " WHERE first_name = 'Kristan' AND last_name = 'Mancini'";
# query($db_handle, $query);
###################################################################### ######################################################################
## Special case - Equities / Loans / Petty Cash ## Special case - Equities / Loans / Petty Cash
@@ -1380,57 +1321,89 @@ print("Set up Petty Cash...\n");
# Add the first loan # Add the first loan
# debit: Equity credit: Loan # debit: Equity credit: Loan
addRow('transactions', addRow('transactions',
{ 'stamp' => datefmt('03/25/2009 16:00'), { 'type' => 'TRANSFER',
}); 'stamp' => datefmt('03/25/2009 16:00'),
addRow('ledger_entries', });
{ 'effective_date' => $effective_date, addRow('double_entries',
'through_date' => $through_date, { 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'}, 'effective_date' => $effective_date,
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'}, 'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'}, 'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'}, 'amount' => 5000,
'customer_id' => undef, 'comment' => "HTP Loan #1",
'lease_id' => undef, });
'amount' => 5000, addRow('entries',
'comment' => "HTP Loan #1", { 'type' => 'TRANSFER',
}); 'account_id' => $newdb{'lookup'}{'account'}{'Equity'}{'account_id'},
'crdr' => 'DEBIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Equity: HTP Loan #1",
});
addRow('entries',
{ 'type' => 'TRANSFER',
'account_id' => $newdb{'lookup'}{'account'}{'Loan'}{'account_id'},
'crdr' => 'CREDIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Loan: HTP Loan #1",
});
# Add the second loan # Add the second loan
# debit: Equity credit: Loan # debit: Equity credit: Loan
addRow('transactions', addRow('transactions',
{ 'stamp' => datefmt('04/01/2009 16:00'), { 'type' => 'TRANSFER',
}); 'stamp' => datefmt('04/01/2009 16:00'),
addRow('ledger_entries', });
{ 'effective_date' => $effective_date, addRow('double_entries',
'through_date' => $through_date, { 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'}, 'effective_date' => $effective_date,
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'}, 'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'}, 'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'}, 'amount' => 1000,
'customer_id' => undef, 'comment' => "HTP Loan #2",
'lease_id' => undef, });
'amount' => 1000, addRow('entries',
'comment' => "HTP Loan #2", { 'type' => 'TRANSFER',
}); 'account_id' => $newdb{'lookup'}{'account'}{'Equity'}{'account_id'},
'crdr' => 'DEBIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Equity: HTP Loan #2",
});
addRow('entries',
{ 'type' => 'TRANSFER',
'account_id' => $newdb{'lookup'}{'account'}{'Loan'}{'account_id'},
'crdr' => 'CREDIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Loan: HTP Loan #2",
});
# Cheat for now, using equity for Petty Cash # Cheat for now, using equity for Petty Cash
# debit: Petty Cash credit: Equity # debit: Petty Cash credit: Equity
addRow('transactions', addRow('transactions',
{ 'stamp' => datefmt('03/25/2009 16:00'), { 'type' => 'TRANSFER',
}); 'stamp' => datefmt('03/25/2009 16:00'),
addRow('ledger_entries', });
{ 'effective_date' => $effective_date, addRow('double_entries',
'through_date' => $through_date, { 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'}, 'effective_date' => $effective_date,
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'}, 'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Petty Cash'}{'ledger_id'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Petty Cash'}{'ledger_id'}, 'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'}, 'amount' => 750,
'customer_id' => undef, 'comment' => "Petty Cash Funding",
'lease_id' => undef, });
'amount' => 750, addRow('entries',
'comment' => "Petty Cash Funding", { 'type' => 'TRANSFER',
}); 'account_id' => $newdb{'lookup'}{'account'}{'Petty Cash'}{'account_id'},
'crdr' => 'DEBIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Petty Cash: Petty Cash Funding",
});
addRow('entries',
{ 'type' => 'TRANSFER',
'account_id' => $newdb{'lookup'}{'account'}{'Equity'}{'account_id'},
'crdr' => 'CREDIT',
'double_entry_id' => $newdb{'tables'}{'double_entries'}{'autoid'},
'comment' => "Equity: Petty Cash Funding",
});
###################################################################### ######################################################################
## Build the Database ## Build the Database
@@ -1492,3 +1465,14 @@ $query = "UPDATE pmgr_units U, pmgr_leases L
query($db_handle, $query); query($db_handle, $query);
######################################################################
## Invoice/Receipt totals
print("Set Invoice/Receipt Totals...\n");
$query = "UPDATE pmgr_transactions T, pmgr_double_entries DE
SET T.`amount` = COALESCE(T.`amount`,0) + DE.amount
WHERE DE.transaction_id = T.id";
query($db_handle, $query);

View File

@@ -2,7 +2,7 @@
class AccountsController extends AppController { class AccountsController extends AppController {
var $uses = array('Account', 'LedgerEntry'); var $uses = array('Account', 'DoubleEntry');
var $sidemenu_links = var $sidemenu_links =
array(array('name' => 'Accounts', 'header' => true), array(array('name' => 'Accounts', 'header' => true),
@@ -68,40 +68,15 @@ class AccountsController extends AppController {
array(// Models array(// Models
'CurrentLedger' => array 'CurrentLedger' => array
(// Models (// Models
'LedgerEntry' 'DoubleEntry'
/* REVISIT <AP> 20090615:
* I'll remove this 'conditions' section on a future checkin,
* after I've proven out the %{MODEL_ALIAS} feature will be
* sticking around.
=> array
('conditions' =>
array('OR' =>
array('LedgerEntry.debit_ledger_id = CurrentLedger.id',
'LedgerEntry.credit_ledger_id = CurrentLedger.id'),
),
),
* END REVISIT
*/
), ),
), ),
); );
} }
function jqGridDataFields(&$params, &$model) { function jqGridDataFields(&$params, &$model) {
return array return array_merge(array('Account.*'),
('Account.*', $this->Account->Ledger->DoubleEntry->debitCreditFields('DoubleEntry', 'CurrentLedger'));
'SUM(IF(LedgerEntry.debit_ledger_id = CurrentLedger.id,
LedgerEntry.amount, NULL)) AS debits',
'SUM(IF(LedgerEntry.credit_ledger_id = CurrentLedger.id,
LedgerEntry.amount, NULL)) AS credits',
"SUM(IF(Account.type IN ('ASSET', 'EXPENSE'),
IF(LedgerEntry.debit_ledger_id = CurrentLedger.id, 1, -1),
IF(LedgerEntry.credit_ledger_id = CurrentLedger.id, 1, -1)
) * IF(LedgerEntry.amount, LedgerEntry.amount, 0)
) AS balance",
'COUNT(LedgerEntry.id) AS entries');
} }
function jqGridDataConditions(&$params, &$model) { function jqGridDataConditions(&$params, &$model) {
@@ -191,7 +166,7 @@ class AccountsController extends AppController {
('Account' => ('Account' =>
array('fields' => array('name')), array('fields' => array('name')),
'LedgerEntry' => 'DoubleEntry' =>
array('fields' => array('id', 'amount'), array('fields' => array('id', 'amount'),
'MonetarySource' => 'MonetarySource' =>
@@ -206,7 +181,7 @@ class AccountsController extends AppController {
), ),
'fields' => false, 'fields' => false,
'conditions' => array(array('Ledger.id' => $ledger_id), 'conditions' => array(array('Ledger.id' => $ledger_id),
array('LedgerEntry.id IS NOT NULL'), array('DoubleEntry.id IS NOT NULL'),
), ),
)); ));
@@ -271,7 +246,7 @@ class AccountsController extends AppController {
// Get all ledger entries of the CURRENT ledger // Get all ledger entries of the CURRENT ledger
$entries = $this->Account->findLedgerEntries($id); $entries = $this->Account->findLedgerEntries($id);
$account['CurrentLedger']['LedgerEntry'] = $entries['Entries']; $account['CurrentLedger']['DoubleEntry'] = $entries['Entries'];
// Summarize each ledger // Summarize each ledger
foreach($account['Ledger'] AS &$ledger) foreach($account['Ledger'] AS &$ledger)
@@ -294,4 +269,8 @@ class AccountsController extends AppController {
$this->set(compact('account', 'title', 'stats')); $this->set(compact('account', 'title', 'stats'));
} }
function tst($id) {
$entries = $this->Account->ledgerEntries($id, true);
pr($entries);
}
} }

View File

@@ -0,0 +1,335 @@
<?php
class DoubleEntriesController extends AppController {
var $sidemenu_links = array();
/**************************************************************************
**************************************************************************
**************************************************************************
* override: sideMenuLinks
* - Generates controller specific links for the side menu
*/
function sideMenuLinks() {
return array_merge(parent::sideMenuLinks(), $this->sidemenu_links);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* virtuals: jqGridData
* - With the application controller handling the jqGridData action,
* these virtual functions ensure that the correct data is passed
* to jqGrid.
*/
function jqGridDataSetup(&$params) {
parent::jqGridDataSetup($params);
if (isset($params['custom']['collected_account_id']))
$params['custom']['account_id'] = $params['custom']['collected_account_id'];
}
function jqGridDataTables(&$params, &$model) {
$link =
array(// Models
'Transaction' =>
array('fields' => array('id', 'stamp'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Lease' =>
array('fields' => array('id', 'number'),
'Unit' =>
array('fields' => array('id', 'name'),
),
),
);
if ($params['action'] == 'collected' || $params['action'] == 'reconcile') {
$link['Payment'] = array('Account' => array('alias' => 'PaymentAccount'),
'Receipt');
$link['Charge'] = array('Account' => array('alias' => 'ChargeAccount'),
'Invoice');
}
if (isset($params['custom']['ledger_id'])) {
$link['Ledger'] =
array('fields' => array('id', 'sequence'),
'Account' =>
array('fields' => array('id', 'name'),
),
);
}
return array('link' => $link);
}
function jqGridDataFields(&$params, &$model) {
if ($params['action'] == 'collected') {
$fields[] = ('SUM(COALESCE(AppliedPayment.amount,0)' .
' + COALESCE(AppliedCharge.amount,0)) AS applied');
$fields[] = 'MAX(Receipt.stamp) AS last_paid';
}
elseif ($params['action'] == 'reconcile') {
$fields[] = array("IF(Entry.type = 'CHARGE',",
" COALESCE(AppliedCharge.amount,0),",
" COALESCE(AppliedPayment.amount,0))",
" AS 'applied'");
$fields[] = array("Entry.amount - (",
"IF(Entry.type = 'CHARGE',",
" COALESCE(AppliedCharge.amount,0),",
" COALESCE(AppliedPayment.amount,0))",
") AS 'balance'");
}
return $fields;
}
function jqGridDataConditions(&$params, &$model) {
if ($params['action'] === 'collected') {
if (!isset($params['custom']['account_id']))
die("INTERNAL ERROR: ACCOUNT ID NOT SET");
extract($params['custom']);
if (!empty($collected_from_date))
$conditions[]
= array('Receipt.stamp >=' =>
$this->DoubleEntry->Transaction->dateFormatBeforeSave($collected_from_date));
if (!empty($collected_through_date))
$conditions[]
= array('Receipt.stamp <=' =>
$this->DoubleEntry->Transaction->dateFormatBeforeSave($collected_through_date . ' 23:59:59'));
if (isset($collected_payment_accounts))
$conditions[] = array('PaymentAccount.id' => $collected_payment_accounts);
else
$conditions[] = array('NOT' => array(array('PaymentAccount.id' => null)));
}
if ($params['action'] === 'ledger') {
$ledger_id = $params['custom']['ledger_id'];
$conditions[] = array('Ledger.id' => $ledger_id);
}
if (isset($params['custom']['reconcile_id'])) {
$conditions[] = array('OR' =>
array('AppliedCharge.id' => $reconcile_id),
array('AppliedPayment.id' => $reconcile_id));
}
if (isset($params['custom']['account_id'])) {
$account_id = $params['custom']['account_id'];
$conditions[] = array('Account.id' => $account_id);
}
if (isset($params['custom']['customer_id'])) {
$conditions[] =
array('Customer.id' => $params['custom']['customer_id']);
}
if (isset($params['custom']['lease_id'])) {
$conditions[] =
array('Lease.id' => $params['custom']['lease_id']);
}
if (isset($params['custom']['transaction_id'])) {
$conditions[] =
array('Transaction.id' => $params['custom']['transaction_id']);
}
return $conditions;
}
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
$links['Transaction'] = array('id');
$links['Entry'] = array('id');
$links['Account'] = array('controller' => 'accounts', 'name');
$links['DebitAccount'] = array('controller' => 'accounts', 'name');
$links['CreditAccount'] = array('controller' => 'accounts', 'name');
$links['MonetarySource'] = array('name');
$links['Customer'] = array('name');
$links['Lease'] = array('number');
$links['Unit'] = array('name');
return parent::jqGridRecordLinks($params, $model, $records, $links);
}
function jqGridDataGroup(&$params, &$model) {
return parent::jqGridDataGroup($params, $model);
}
function jqGridDataOrder(&$params, &$model, $index, $direction) {
/* if ($index === 'balance') */
/* return ($index .' '. $direction); */
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
if ($index === 'Transaction.stamp') {
$order[] = 'Entry.id ' . $direction;
}
return $order;
}
function jqGridDataRecords(&$params, &$model, $query) {
if ($params['action'] === 'collected') {
$tquery = array_diff_key($query, array(/*'fields'=>1,*/'group'=>1,'limit'=>1,'order'=>1));
$tquery['group'] = array('AppliedPayment.id');
$tquery['fields'] = array("IF(Entry.type = 'CHARGE',",
" SUM(COALESCE(AppliedCharge.amount,0)),",
" SUM(COALESCE(AppliedPayment.amount,0)))",
" AS 'applied'",
"Charge.amount - (",
"IF(Entry.type = 'CHARGE',",
" SUM(COALESCE(AppliedCharge.amount,0)),",
" SUM(COALESCE(AppliedPayment.amount,0)))",
") AS 'balance'",
);
$total = $model->find('first', $tquery);
$params['userdata']['total'] = $total[0]['applied'];
$params['userdata']['balance'] = $total[0]['balance'];
}
return parent::jqGridDataRecords($params, $model, $query);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: reverse the ledger entry
*/
function reverse($id) {
$this->DoubleEntry->reverse($id);
$this->redirect(array('action'=>'view', $id));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: view
* - Displays information about a specific entry
*/
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid Item.', true));
$this->redirect(array('controller' => 'accounts', 'action'=>'index'));
}
// Get the Entry and related fields
$entry = $this->DoubleEntry->find
('first',
array('contain' => array
('Transaction' => array('fields' => array('id', 'stamp')),
'Customer' => array('fields' => array('id', 'name')),
'Lease' => array('fields' => array('id')),
'DebitLedger' => array('fields' => array('id', 'sequence'),
'Account' => array('id', 'name', 'type', 'trackable')),
'CreditLedger' => array('fields' => array('id', 'sequence'),
'Account' => array('id', 'name', 'type', 'trackable')),
'DebitEntry'/* => array('fields' => array('id'))*/,
'CreditEntry'/* => array('fields' => array('id'))*/,
'Entry' => array('fields' => array('id')),
),
//'fields' => array('DoubleEntry.*'),
'conditions' => array('DoubleEntry.id' => $id),
));
pr($entry);
$stats = $this->DoubleEntry->stats($id);
foreach (array('debit', 'credit') AS $crdr) {
$CrDr = ucfirst($crdr);
$entry[$CrDr.'Ledger']['Account']['ftype'] =
$this->DoubleEntry->Ledger->Account->fundamentalType
($entry[$CrDr.'Ledger']['Account']['type']);
$stats[$crdr]['amount_reconciled'] = null;
$stats[$crdr]['amount_remaining'] = null;
}
/* // Get the reconciliation balances for this ledger entry */
/* $stats['debit']['amount_reconciled'] = $stats['debit_amount_reconciled']; */
/* $stats['credit']['amount_reconciled'] = $stats['credit_amount_reconciled']; */
/* if ($entry['DebitLedger']['Account']['trackable']) */
/* $stats['debit']['amount_remaining'] = $entry['Entry']['amount'] - $stats['debit']['amount_reconciled']; */
/* if ($entry['CreditLedger']['Account']['trackable']) */
/* $stats['credit']['amount_remaining'] = $entry['Entry']['amount'] - $stats['credit']['amount_reconciled']; */
/* //pr($stats); */
/* $reconciled = $this->DoubleEntry->findReconciledDoubleEntries($id); */
/* //pr($reconciled); */
// 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->DoubleEntry->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));
}
// Prepare to render.
$title = "Double Ledger Entry #{$entry['DoubleEntry']['id']}";
$this->set(compact('entry', 'title', 'reconciled', 'stats'));
}
function tst($id = null) {
$entry = $this->DoubleEntry->find
('first',
array('contain' => array('Account',
'DoubleEntry',
'Payment' => array('fields' => array('Payment.*'/*, 'AppliedPayment.*'*/),
'DoubleEntry'/* => array('alias' => 'PaymentDoubleEntry')*/),
'Charge' => array('fields' => array('Charge.*'/*, 'AppliedCharge.*'*/),
'DoubleEntry'/* => array('alias' => 'ChargeDoubleEntry')*/),
),
'conditions' => array('Entry.id' => $id),
));
pr($entry);
}
}

View File

@@ -0,0 +1,337 @@
<?php
class EntriesController extends AppController {
var $sidemenu_links = array();
/**************************************************************************
**************************************************************************
**************************************************************************
* override: sideMenuLinks
* - Generates controller specific links for the side menu
*/
function sideMenuLinks() {
return array_merge(parent::sideMenuLinks(), $this->sidemenu_links);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* virtuals: jqGridData
* - With the application controller handling the jqGridData action,
* these virtual functions ensure that the correct data is passed
* to jqGrid.
*/
function jqGridDataSetup(&$params) {
parent::jqGridDataSetup($params);
if (isset($params['custom']['collected_account_id']))
$params['custom']['account_id'] = $params['custom']['collected_account_id'];
}
function jqGridDataTables(&$params, &$model) {
$link =
array(// Models
'Account' =>
array('fields' => array('id', 'name'),
),
'DoubleEntry' => array
('Transaction' =>
array('fields' => array('id', 'stamp'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Lease' =>
array('fields' => array('id', 'number'),
'Unit' =>
array('fields' => array('id', 'name'),
),
),
),
);
if ($params['action'] === 'collected' ||
isset($params['custom']['reconcile_id'])) {
$link['Payment'] = array(//'linkalias' => 'MatchingPayment',
'Account' => array('alias' => 'PaymentAccount'),
'Receipt');
$link['Charge'] = array(//'linkalias' => 'MatchingCharge',
'Account' => array('alias' => 'ChargeAccount'),
'Invoice');
}
if (isset($params['custom']['ledger_id'])) {
$link['Ledger'] =
array('fields' => array('id', 'sequence'),
'Account' =>
array('fields' => array('id', 'name'),
),
);
}
return array('link' => $link);
}
function jqGridDataFields(&$params, &$model) {
$fields = parent::jqGridDataFields($params, $model);
if (count(array_intersect($params['fields'], array('applied'))) == 1)
$fields[] = ('SUM(COALESCE(AppliedPayment.amount,0)' .
' + COALESCE(AppliedCharge.amount,0)) AS applied');
if (isset($params['custom']['reconcile_id'])) {
$fields[] = array("IF(Entry.type = 'CHARGE',",
" COALESCE(AppliedCharge.amount,0),",
" COALESCE(AppliedPayment.amount,0))",
" AS 'applied'");
$fields[] = array("Entry.amount - (",
"IF(Entry.type = 'CHARGE',",
" COALESCE(AppliedCharge.amount,0),",
" COALESCE(AppliedPayment.amount,0))",
") AS 'balance'");
}
if ($params['action'] === 'collected')
$fields[] = 'MAX(Receipt.stamp) AS last_paid';
return $fields;
}
function jqGridDataConditions(&$params, &$model) {
$conditions = parent::jqGridDataConditions($params, $model);
if ($params['action'] === 'collected') {
extract($params['custom']);
if (!isset($params['custom']['account_id']))
die("INTERNAL ERROR: ACCOUNT ID NOT SET");
if (!empty($collected_from_date))
$conditions[]
= array('Receipt.stamp >=' =>
$this->Entry->Transaction->dateFormatBeforeSave($collected_from_date));
if (!empty($collected_through_date))
$conditions[]
= array('Receipt.stamp <=' =>
$this->Entry->Transaction->dateFormatBeforeSave($collected_through_date . ' 23:59:59'));
if (isset($collected_payment_accounts))
$conditions[] = array('PaymentAccount.id' => $collected_payment_accounts);
else
$conditions[] = array('NOT' => array(array('PaymentAccount.id' => null)));
}
if (isset($params['custom']['ledger_id'])) {
$ledger_id = $params['custom']['ledger_id'];
$conditions[] = array('Ledger.id' => $ledger_id);
}
if (isset($params['custom']['reconcile_id'])) {
$conditions[] = array('OR' =>
array('AppliedCharge.id' => $reconcile_id),
array('AppliedPayment.id' => $reconcile_id));
}
if (isset($params['custom']['account_id'])) {
$account_id = $params['custom']['account_id'];
$conditions[] = array('Account.id' => $account_id);
}
if (isset($params['custom']['customer_id'])) {
$conditions[] =
array('Customer.id' => $params['custom']['customer_id']);
}
if (isset($params['custom']['lease_id'])) {
$conditions[] =
array('Lease.id' => $params['custom']['lease_id']);
}
if (isset($params['custom']['transaction_id'])) {
$conditions[] =
array('Transaction.id' => $params['custom']['transaction_id']);
}
return $conditions;
}
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
$links['Transaction'] = array('id');
$links['Entry'] = array('id');
$links['Account'] = array('controller' => 'accounts', 'name');
$links['DebitAccount'] = array('controller' => 'accounts', 'name');
$links['CreditAccount'] = array('controller' => 'accounts', 'name');
$links['MonetarySource'] = array('name');
$links['Customer'] = array('name');
$links['Lease'] = array('number');
$links['Unit'] = array('name');
return parent::jqGridRecordLinks($params, $model, $records, $links);
}
function jqGridDataGroup(&$params, &$model) {
return parent::jqGridDataGroup($params, $model);
}
function jqGridDataOrder(&$params, &$model, $index, $direction) {
/* if ($index === 'balance') */
/* return ($index .' '. $direction); */
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
if ($index === 'Transaction.stamp') {
$order[] = 'Entry.id ' . $direction;
}
return $order;
}
function jqGridDataRecords(&$params, &$model, $query) {
if ($params['action'] === 'collected') {
$tquery = array_diff_key($query, array(/*'fields'=>1,*/'group'=>1,'limit'=>1,'order'=>1));
$tquery['group'] = array('AppliedPayment.id');
$tquery['fields'] = array("IF(Entry.type = 'CHARGE',",
" SUM(COALESCE(AppliedCharge.amount,0)),",
" SUM(COALESCE(AppliedPayment.amount,0)))",
" AS 'applied'",
"Charge.amount - (",
"IF(Entry.type = 'CHARGE',",
" SUM(COALESCE(AppliedCharge.amount,0)),",
" SUM(COALESCE(AppliedPayment.amount,0)))",
") AS 'balance'",
);
$total = $model->find('first', $tquery);
$params['userdata']['total'] = $total[0]['applied'];
$params['userdata']['balance'] = $total[0]['balance'];
}
return parent::jqGridDataRecords($params, $model, $query);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: reverse the ledger entry
*/
function reverse($id) {
$this->Entry->reverse($id);
$this->redirect(array('action'=>'view', $id));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: view
* - Displays information about a specific entry
*/
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid Item.', true));
$this->redirect(array('controller' => 'accounts', 'action'=>'index'));
}
// Get the Entry and related fields
$entry = $this->Entry->find
('first',
array('contain' => array
('Account' => array('id', 'name', 'type', 'trackable'),
'DoubleEntry' => array
(//'fields' => array('id'),
'DebitEntry' => array('fields' => array('id', 'crdr')),
'CreditEntry' => array('fields' => array('id', 'crdr')),
'Transaction' => array('fields' => array('id', 'stamp')),
'Customer' => array('fields' => array('id', 'name')),
'Lease' => array('fields' => array('id')),
),
),
'conditions' => array('Entry.id' => $id),
));
$entry['Entry']['opposite_crdr'] =
ucfirst($this->Entry->Account->fundamentalOpposite($entry['Entry']['crdr']));
$entry['Entry']['matching_entry_id'] =
$entry['DoubleEntry'][
ucfirst(strtolower($entry['Entry']['opposite_crdr'])) .
'Entry']['id'];
/* if ($entry['DoubleEntry']['DebitEntry']['id'] == $entry['Entry']['id']) */
/* $entry['Entry']['matching_entry_id'] = $entry['DoubleEntry']['CreditEntry']['id']; */
/* if ($entry['DoubleEntry']['CreditEntry']['id'] == $entry['Entry']['id']) */
/* $entry['Entry']['matching_entry_id'] = $entry['DoubleEntry']['DebitEntry']['id']; */
//pr(compact('entry'));
$reconciled = $this->Entry->reconciledEntries($id);
//pr(compact('reconciled'));
/* // 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->Entry->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)); */
/* } */
// Prepare to render.
$title = "Ledger Entry #{$entry['Entry']['id']}";
$this->set(compact('entry', 'title', 'reconciled'));
}
function tst($id = null) {
$entry = $this->Entry->find
('first',
array('contain' => array('Account',
'DoubleEntry',
'Payment' => array('fields' => array('Payment.*'/*, 'AppliedPayment.*'*/),
'DoubleEntry'/* => array('alias' => 'PaymentDoubleEntry')*/),
'Charge' => array('fields' => array('Charge.*'/*, 'AppliedCharge.*'*/),
'DoubleEntry'/* => array('alias' => 'ChargeDoubleEntry')*/),
),
'conditions' => array('Entry.id' => $id),
));
pr($entry);
}
}

View File

@@ -238,6 +238,56 @@ class LeasesController extends AppController {
*/ */
function apply_deposit($id) { function apply_deposit($id) {
// Create some models for convenience
$A = new Account();
if ($this->data) {
// Handle the move out based on the data given
//pr($this->data);
// 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['LedgerEntry'] 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->LedgerEntry->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;
$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(); $A = new Account();
$lease = $this->Lease->find $lease = $this->Lease->find
@@ -351,6 +401,18 @@ class LeasesController extends AppController {
*/ */
function refund($id) { 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->findSecurityDeposits($id); */
/* $outstanding_deposit = $deposits['summary']['balance']; */
/* $this->set(compact('lease', 'title', */
/* 'outstanding_deposit', */
/* 'outstanding_balance')); */
} }

View File

@@ -1,467 +0,0 @@
<?php
class LedgerEntriesController extends AppController {
var $sidemenu_links = array();
/**************************************************************************
**************************************************************************
**************************************************************************
* override: sideMenuLinks
* - Generates controller specific links for the side menu
*/
function sideMenuLinks() {
return array_merge(parent::sideMenuLinks(), $this->sidemenu_links);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* virtuals: jqGridData
* - With the application controller handling the jqGridData action,
* these virtual functions ensure that the correct data is passed
* to jqGrid.
*/
function jqGridDataSetup(&$params) {
parent::jqGridDataSetup($params);
if (isset($params['custom']['ar_account'])) {
$params['custom']['account_id'] =
$this->LedgerEntry->DebitLedger->Account->accountReceivableAccountID();
}
}
function jqGridDataTables(&$params, &$model) {
$link =
array(// Models
'Transaction' =>
array('fields' => array('id', 'stamp'),
),
'MonetarySource' =>
array('fields' => array('id', 'name'),
),
'Customer' =>
array('fields' => array('id', 'name'),
),
'Lease' =>
array('fields' => array('id', 'number'),
'Unit' =>
array('fields' => array('id', 'name'),
),
),
);
if (isset($params['custom']['account_ftype'])) {
$ftype = $params['custom']['account_ftype'];
$ftype = ucfirst($ftype);
//$ftype = $this->LedgerEntry->DebitLedger->Account->fundamentalOpposite($ftype);
$link[$ftype . 'Ledger'] =
array('fields' => array('id', 'sequence'),
'Account' => array('class' => 'Account',
'fields' => array('id', 'name'),
),
);
}
elseif (isset($params['custom']['ledger_id'])) {
$ledger_id = $params['custom']['ledger_id'];
$link['Ledger'] =
array('fields' => array('id', 'sequence'),
'conditions' => ("Ledger.id = IF(LedgerEntry.debit_ledger_id = $ledger_id," .
" LedgerEntry.credit_ledger_id," .
" LedgerEntry.debit_ledger_id)"),
'Account' => array(
'fields' => array('id', 'name'),
),
);
}
elseif ($params['action'] === 'collected') {
// Income / Receipt / Money
// debit: A/R credit: Income <-- this entry
// debit: Receipt credit: A/R <-- ReceiptLedgerEntry, below
// debit: Money credit: Receipt <-- MoneyLedgerEntry, below
$link['CreditLedger'] =
array('fields' => 'sequence',
'Account' =>
array('fields' => array('id', 'name'),
),
);
// We're searching for the Receipt<->A/R entries,
// which are debits on the A/R account. Find the
// reconciling entries to that A/R debit.
$link['DebitReconciliationLedgerEntry'] =
array('alias' => 'ReceiptLedgerEntry',
'Transaction' =>
array('alias' => 'ReceiptTransaction'),
// Credit Ledger should be A/R;
// Debit Ledger should be Receipt
'DebitLedger' =>
array('alias' => 'ReceiptLedger',
'Account' => array('alias' => 'ReceiptAccount'),
),
// Finally, the Money (Cash/Check/etc) Entry is the one
// which reconciles our ReceiptLedgerEntry debit
'DebitReconciliationLedgerEntry' =>
array('alias' => 'MoneyLedgerEntry',
'linkalias' => 'MoneyLedgerEntryR',
// Credit Ledger should be Receipt;
// Debit Ledger should be our Money Account
'DebitLedger' =>
array('alias' => 'MoneyLedger',
'Account' =>
array('alias' => 'MoneyAccount'),
),
),
);
}
else {
$link['DebitLedger'] =
array('fields' => array('id', 'sequence'),
'DebitAccount' => array('class' => 'Account',
'fields' => array('id', 'name'),
),
);
$link['CreditLedger'] =
array('fields' => array('id', 'sequence'),
'CreditAccount' => array('class' => 'Account',
'fields' => array('id', 'name'),
),
);
}
if (isset($params['custom']['account_id'])) {
$account_id = $params['custom']['account_id'];
$link['Ledger'] =
array('fields' => array('id', 'sequence'),
'conditions' => ("Ledger.id = IF(DebitLedger.account_id = $account_id," .
" LedgerEntry.credit_ledger_id," .
" LedgerEntry.debit_ledger_id)"),
'Account' => array(
'fields' => array('id', 'name'),
),
);
}
if (isset($params['custom']['reconcile_id'])) {
$ftype = $params['custom']['account_ftype'];
$ftype = $this->LedgerEntry->DebitLedger->Account->fundamentalOpposite($ftype);
$ftype = ucfirst($ftype);
$link[$ftype.'ReconciliationLedgerEntry'] =
array('fields' => array('Reconciliation.amount'));
}
return array('link' => $link);
}
function jqGridDataFields(&$params, &$model) {
$ledger_id = (isset($params['custom']['ledger_id'])
? $params['custom']['ledger_id']
: null);
$account_id = (isset($params['custom']['account_id'])
? $params['custom']['account_id']
: null);
$account_type = (isset($params['custom']['account_type'])
? $params['custom']['account_type']
: null);
$fields = $model->ledgerContextFields2($ledger_id, $account_id, $account_type);
if (count(array_intersect($params['fields'], array('applied'))) == 1)
$fields[] = 'SUM(Reconciliation.amount) AS applied';
if ($params['action'] === 'collected')
$fields[] = 'MAX(ReceiptTransaction.stamp) AS last_paid';
return $fields;
}
function jqGridDataConditions(&$params, &$model) {
$ledger_id = (isset($params['custom']['ledger_id'])
? $params['custom']['ledger_id']
: null);
$account_type = (isset($params['custom']['account_type'])
? $params['custom']['account_type']
: null);
$conditions = parent::jqGridDataConditions($params, $model);
if ($params['action'] === 'collected') {
extract($params['custom']);
if (isset($collected_account_id))
$conditions[] = array('Account.id' => $params['custom']['collected_account_id']);
else
die("INTERNAL ERROR: COLLECTED ACCOUNT ID NOT SET");
if (!empty($collected_from_date))
$conditions[]
= array('ReceiptTransaction.stamp >=' =>
$this->LedgerEntry->Transaction->dateFormatBeforeSave($collected_from_date));
if (!empty($collected_through_date))
$conditions[]
= array('ReceiptTransaction.stamp <=' =>
$this->LedgerEntry->Transaction->dateFormatBeforeSave($collected_through_date . ' 23:59:59'));
if (isset($collected_payment_accounts))
$conditions[] = array('MoneyAccount.id' => $collected_payment_accounts);
else
$conditions[] = array('NOT' => array(array('MoneyAccount.id' => null)));
}
if ($params['action'] === 'ledger') {
$conditions[] = $model->ledgerContextConditions($ledger_id, $account_type);
}
if (isset($params['custom']['reconcile_id'])) {
$ftype = $params['custom']['account_ftype'];
//$ftype = $this->LedgerEntry->DebitLedger->Account->fundamentalOpposite($ftype);
$conditions[] = array('Reconciliation.'.$ftype.'_ledger_entry_id' => $params['custom']['reconcile_id']);
}
if (isset($params['custom']['account_id'])) {
$conditions[] =
array('OR' =>
array(array('CreditAccount.id' => $params['custom']['account_id']),
array('DebitAccount.id' => $params['custom']['account_id'])));
}
if (isset($params['custom']['customer_id'])) {
$conditions[] =
array('Customer.id' => $params['custom']['customer_id']);
/* $Account = new Account(); */
/* if (isset($params['custom']['account_ftype']) || */
/* isset($params['custom']['ledger_id'])) { */
/* $conditions[] = */
/* array('OR' => array('Account.id' => $Account->invoiceAccountID(), */
/* 'Account.id' => $Account->receiptAccountID())); */
/* } else { */
/* $conditions[] = */
/* array('OR' => array('DebitAccount.id' => $Account->invoiceAccountID(), */
/* //'CreditAccount.id' => $Account->invoiceAccountID(), */
/* //'DebitAccount.id' => $Account->receiptAccountID(), */
/* 'CreditAccount.id' => $Account->receiptAccountID(), */
/* )); */
/* } */
}
if (isset($params['custom']['lease_id'])) {
$conditions[] =
array('Lease.id' => $params['custom']['lease_id']);
/* $Account = new Account(); */
/* if (isset($params['custom']['account_ftype']) || */
/* isset($params['custom']['ledger_id'])) { */
/* $conditions[] = */
/* array('OR' => array('Account.id' => $Account->invoiceAccountID(), */
/* 'Account.id' => $Account->receiptAccountID())); */
/* } else { */
/* $conditions[] = */
/* array('OR' => array('DebitAccount.id' => $Account->invoiceAccountID(), */
/* //'CreditAccount.id' => $Account->invoiceAccountID(), */
/* //'DebitAccount.id' => $Account->receiptAccountID(), */
/* 'CreditAccount.id' => $Account->receiptAccountID(), */
/* )); */
/* } */
}
if (isset($params['custom']['transaction_id'])) {
$conditions[] =
array('Transaction.id' => $params['custom']['transaction_id']);
}
if (isset($params['custom']['monetary_source_id'])) {
$conditions[] =
array('MonetarySource.id' => $params['custom']['monetary_source_id']);
}
return $conditions;
}
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
$links['Transaction'] = array('id');
$links['LedgerEntry'] = array('id');
$links['Account'] = array('controller' => 'accounts', 'name');
$links['DebitAccount'] = array('controller' => 'accounts', 'name');
$links['CreditAccount'] = array('controller' => 'accounts', 'name');
$links['MonetarySource'] = array('name');
$links['Customer'] = array('name');
$links['Lease'] = array('number');
$links['Unit'] = array('name');
return parent::jqGridRecordLinks($params, $model, $records, $links);
}
function jqGridDataGroup(&$params, &$model) {
if (isset($params['custom']['group_by_tx']) && $params['custom']['group_by_tx'])
return $model->alias.'.transaction_id';
return parent::jqGridDataGroup($params, $model);
}
function jqGridDataOrder(&$params, &$model, $index, $direction) {
/* if ($index === 'balance') */
/* return ($index .' '. $direction); */
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
if ($index === 'Transaction.stamp') {
$order[] = 'LedgerEntry.id ' . $direction;
}
return $order;
}
function jqGridDataRecords(&$params, &$model, $query) {
if ($params['action'] === 'collected') {
$tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1));
$tquery['fields'] = array('SUM(Reconciliation.amount) AS applied');
$total = $model->find('first', $tquery);
$params['userdata']['total'] = $total[0]['applied'];
}
return parent::jqGridDataRecords($params, $model, $query);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: reverse the ledger entry
*/
function reverse($id) {
$this->LedgerEntry->reverse($id);
$this->redirect(array('action'=>'view', $id));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* action: view
* - Displays information about a specific entry
*/
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid Item.', true));
$this->redirect(array('controller' => 'accounts', 'action'=>'index'));
}
// Get the LedgerEntry and related fields
$entry = $this->LedgerEntry->find
('first',
array('contain' => array('MonetarySource.id',
'MonetarySource.name',
'Transaction.id',
'Transaction.stamp',
'DebitLedger.id',
'DebitLedger.sequence',
'DebitLedger.account_id',
'CreditLedger.id',
'CreditLedger.sequence',
'CreditLedger.account_id',
'Customer.id',
'Customer.name',
'Lease.id',
),
'fields' => array('LedgerEntry.*'),
'conditions' => array('LedgerEntry.id' => $id),
));
//pr($entry);
// Because 'DebitLedger' and 'CreditLedger' both relate to 'Account',
// CakePHP will not include them in the LedgerEntry->find (or so it
// seems). We'll have to break out each Account separately.
// Get the Account from DebitLedger
$entry['DebitLedger'] += $this->LedgerEntry->DebitLedger->Account->find
('first',
array('contain' => true,
'fields' => array('Account.id', 'Account.name', 'Account.type', 'Account.trackable'),
'conditions' => array('Account.id' => $entry['DebitLedger']['account_id']),
));
$entry['DebitLedger']['Account']['ftype'] =
$this->LedgerEntry->DebitLedger->Account
->fundamentalType($entry['DebitLedger']['Account']['type']);
// Get the Account from CreditLedger
$entry['CreditLedger'] += $this->LedgerEntry->CreditLedger->Account->find
('first',
array('contain' => true,
'fields' => array('Account.id', 'Account.name', 'Account.type', 'Account.trackable'),
'conditions' => array('Account.id' => $entry['CreditLedger']['account_id']),
));
$entry['CreditLedger']['Account']['ftype'] =
$this->LedgerEntry->CreditLedger->Account
->fundamentalType($entry['CreditLedger']['Account']['type']);
// Get the reconciliation balances for this ledger entry
$stats = $this->LedgerEntry->stats($id);
$stats['debit']['amount_reconciled'] = $stats['debit_amount_reconciled'];
$stats['credit']['amount_reconciled'] = $stats['credit_amount_reconciled'];
if ($entry['DebitLedger']['Account']['trackable'])
$stats['debit']['amount_remaining'] = $entry['LedgerEntry']['amount'] - $stats['debit']['amount_reconciled'];
if ($entry['CreditLedger']['Account']['trackable'])
$stats['credit']['amount_remaining'] = $entry['LedgerEntry']['amount'] - $stats['credit']['amount_reconciled'];
//pr($stats);
$reconciled = $this->LedgerEntry->findReconciledLedgerEntries($id);
//pr($reconciled);
// 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->LedgerEntry->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));
}
// Prepare to render.
$title = "Double Ledger Entry #{$entry['LedgerEntry']['id']}";
$this->set(compact('entry', 'title', 'reconciled', 'stats'));
}
}

View File

@@ -58,26 +58,16 @@ class LedgersController extends AppController {
('link' => ('link' =>
array(// Models array(// Models
'Account', 'Account',
'LedgerEntry', 'DoubleEntry',
'Close', 'Close',
), ),
); );
} }
function jqGridDataFields(&$params, &$model) { function jqGridDataFields(&$params, &$model) {
return array return array_merge(array('Ledger.*',
('Ledger.*', 'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence'),
'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence', $this->Ledger->DoubleEntry->debitCreditFields());
'SUM(IF(LedgerEntry.debit_ledger_id = Ledger.id,
LedgerEntry.amount, NULL)) AS debits',
'SUM(IF(LedgerEntry.credit_ledger_id = Ledger.id,
LedgerEntry.amount, NULL)) AS credits',
"SUM(IF(Account.type IN ('ASSET', 'EXPENSE'),
IF(LedgerEntry.debit_ledger_id = Ledger.id, 1, -1),
IF(LedgerEntry.credit_ledger_id = Ledger.id, 1, -1)
) * IF(LedgerEntry.amount, LedgerEntry.amount, 0)
) AS balance",
'COUNT(LedgerEntry.id) AS entries');
} }
function jqGridDataConditions(&$params, &$model) { function jqGridDataConditions(&$params, &$model) {

View File

@@ -1,13 +1,6 @@
<?php <?php
class Account extends AppModel { class Account extends AppModel {
var $name = 'Account';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
'external_name' => array('notempty')
);
var $hasOne = array( var $hasOne = array(
'CurrentLedger' => array( 'CurrentLedger' => array(
'className' => 'Ledger', 'className' => 'Ledger',
@@ -327,25 +320,46 @@ class Account extends AppModel {
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: findLedgerEntries * function: ledgerEntries
* - Returns an array of ledger entries that belong to the given * - Returns an array of ledger entries that belong to the given
* account, either just from the current ledger, or from all ledgers. * account, either just from the current ledger, or from all ledgers.
*/ */
function findLedgerEntries($id, $all = false, $cond = null, $link = null) { function ledgerEntries($id, $all = false, $cond = null, $link = null) {
/* pr(array('function' => 'Account::findLedgerEntries', */ /* pr(array('function' => 'Account::findLedgerEntries', */
/* 'args' => compact('id', 'all', 'cond', 'link'), */ /* 'args' => compact('id', 'all', 'cond', 'link'), */
/* )); */ /* )); */
$entries = array(); /* $this->Entry->find */
foreach ($this->ledgers($id, $all) AS $ledger_id) { /* ('all', array */
$ledger_entries = $this->Ledger->findLedgerEntries /* ('contain' => array(), */
($ledger_id, $this->type($id), $cond, $link); /* 'conditions' => array('Entry.account_id' => $id) */
$entries = array_merge($entries, $ledger_entries); /* )); */
}
$stats = $this->stats($id, $all, $cond); $ledgers = $this->ledgers($id, $all);
$entries = array('Entries' => $entries,
'summary' => $stats['Ledger']); /* $this->Ledger->DoubleEntry->find */
/* ('all', array */
/* ('contain' => array('Ledger'), */
/* 'conditions' => array('OR' => */
/* array('DoubleEntry.debit_ledger_id' => $ledgers), */
/* array('DoubleEntry.credit_ledger_id' => $ledgers)), */
/* 'fields' => */
/* )); */
$entries = $this->Ledger->find
('all', array
('link' =>
array('Account',
'DoubleEntry' => array
('fields' => $this->Ledger->DoubleEntry->debitCreditFields('DoubleEntry', 'Ledger', false)),
),
'conditions' => array('Ledger.id' => $ledgers),
));
/* $stats = $this->stats($id, $all, $cond); */
/* $entries = array('Entries' => $entries, */
/* 'summary' => $stats['Ledger']); */
/* pr(array('function' => 'Account::findLedgerEntries', */ /* pr(array('function' => 'Account::findLedgerEntries', */
/* 'args' => compact('id', 'all', 'cond', 'link'), */ /* 'args' => compact('id', 'all', 'cond', 'link'), */
@@ -379,7 +393,7 @@ class Account extends AppModel {
foreach ($rel_ids AS $rel_id) foreach ($rel_ids AS $rel_id)
$ledger_ids = array_merge($ledger_ids, $this->ledgers($rel_id)); $ledger_ids = array_merge($ledger_ids, $this->ledgers($rel_id));
array_push($cond, $this->Ledger->LedgerEntry->conditionEntryAsCreditOrDebit($ledger_ids)); array_push($cond, $this->Ledger->DoubleEntry->conditionEntryAsCreditOrDebit($ledger_ids));
$entries = $this->findLedgerEntries($id, $all, $cond, $link); $entries = $this->findLedgerEntries($id, $all, $cond, $link);
/* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */ /* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */
@@ -413,28 +427,28 @@ class Account extends AppModel {
('link' => array ('link' => array
('Ledger' => array ('Ledger' => array
('fields' => array(), ('fields' => array(),
"LedgerEntry" => array "DoubleEntry" => array
('class' => "{$ucfund}LedgerEntry", ('class' => "{$ucfund}DoubleEntry",
'fields' => array('id', 'customer_id', 'lease_id', 'amount'), 'fields' => array('id', 'customer_id', 'lease_id', 'amount'),
"ReconciliationLedgerEntry" => array "ReconciliationDoubleEntry" => array
('class' => "{$ucfund}ReconciliationLedgerEntry", ('class' => "{$ucfund}ReconciliationDoubleEntry",
'fields' => array 'fields' => array
("COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'", ("COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'",
"LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'", "DoubleEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
), ),
), ),
), ),
), ),
), ),
'group' => ("LedgerEntry.id" . 'group' => ("DoubleEntry.id" .
" HAVING LedgerEntry.amount" . " HAVING DoubleEntry.amount" .
" <> COALESCE(SUM(Reconciliation.amount),0)"), " <> COALESCE(SUM(Reconciliation.amount),0)"),
'conditions' => $cond, 'conditions' => $cond,
'fields' => array(), 'fields' => array(),
)); ));
$balance = 0; $balance = 0;
foreach ($unreconciled[$fund]['entry'] AS &$entry) { foreach ($unreconciled[$fund]['entry'] AS &$entry) {
$entry = array_merge(array_diff_key($entry["LedgerEntry"], array(0=>true)), $entry = array_merge(array_diff_key($entry["DoubleEntry"], array(0=>true)),
$entry[0]); $entry[0]);
$balance += $entry['balance']; $balance += $entry['balance'];
} }
@@ -448,7 +462,7 @@ class Account extends AppModel {
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: reconcileNewLedgerEntry * function: amountWouldReconcile
* - Returns which ledger entries a new credit/debit would * - Returns which ledger entries a new credit/debit would
* reconcile, and how much. * reconcile, and how much.
* *
@@ -459,7 +473,7 @@ class Account extends AppModel {
* whatever algorithm is simplest. * whatever algorithm is simplest.
*/ */
function reconcileNewLedgerEntry($id, $fundamental_type, $amount, $cond = null) { function amountWouldReconcile($id, $fundamental_type, $amount, $cond = null) {
$ofund = $this->fundamentalOpposite($fundamental_type); $ofund = $this->fundamentalOpposite($fundamental_type);
$unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0)); $unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0));
$applied = 0; $applied = 0;
@@ -498,7 +512,78 @@ class Account extends AppModel {
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: postLedgerEntry * function: reconcileLedgerEntries
* - Returns which ledger entries a new credit/debit would
* reconcile, and how much.
*
* - REVISIT <AP> 20090617
* This should be subject to different algorithms, such
* as apply to oldest charges first, newest first, to fees
* before rent, etc. Until we get there, I'll hardcode
* whatever algorithm is simplest.
*/
function reconcileLedgerEntries($id, $cond = null) {
$unreconciled = $this->findUnreconciledLedgerEntries($id, null, $cond);
pr(compact('unreconciled'));
$entry = array();
foreach (array('debit', 'credit') AS $dc)
$entry[$dc] = array('balance' => 0);
while ($entry['debit'] && $entry['credit']) {
// If/When we've exhausted this/these entries, move the next
foreach (array('debit', 'credit') AS $dc) {
if ($entry[$dc]['balance'] <= 0) {
$entry[$dc] =& current($unreconciled[$dc]['entry']);
next($unreconciled[$dc]['entry']);
$entry[$dc]['applied'] = 0;
continue 2;
}
}
// At this point, both entries are valid with a positive balance
$apply = min($entry['debit']['balance'], $entry['credit']['balance']);
// REVISIT <AP>: 20090716
// NOT YET ENTERING THE RECONCILIATION SO THAT WE CAN TEST
// MUST ADD THE RECONCILIATION ENTRY!!!!
foreach (array('debit', 'credit') AS $dc) {
$entry[$dc]['applied'] += $apply;
$entry[$dc]['reconciled'] += $apply;
$entry[$dc]['balance'] -= $apply;
}
}
foreach (array('debit', 'credit') AS $dc) {
$unreconciled[$dc]['applied'] = 0;
//$unreconciled[$dc]['unapplied'] = 0;
foreach ($unreconciled[$dc]['entry'] AS $i => $entry) {
if (isset($entry[$dc]['applied']))
$unreconciled[$dc]['applied'] += $entry[$dc]['applied'];
else
unset($unreconciled[$dc]['entry'][$i]);
//$unreconciled[$dc]['unapplied'] += $entry[$dc]['balance'];
}
$unreconciled[$dc]['balance'] -= $unreconciled[$dc]['applied'];
}
$unreconciled['debit'] ['unapplied'] = $unreconciled['credit']['balance'];
$unreconciled['credit']['unapplied'] = $unreconciled['debit'] ['balance'];
pr(compact('unreconciled'));
return $unreconciled;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: postDoubleEntry
* - * -
* transaction_data * transaction_data
* - transaction_id (optional... if set all else is ignored) * - transaction_id (optional... if set all else is ignored)
@@ -513,7 +598,7 @@ class Account extends AppModel {
* - name * - name
*/ */
function postLedgerEntry($transaction_data, function postDoubleEntry($transaction_data,
$monetary_data, $monetary_data,
$entry_data, $entry_data,
$reconcile = null) { $reconcile = null) {
@@ -566,7 +651,7 @@ class Account extends AppModel {
// No distinguishing features of Cash, just // No distinguishing features of Cash, just
// use the shared monetary source // use the shared monetary source
$monetary_data['monetary_source_id'] = $monetary_data['monetary_source_id'] =
$this->Ledger->LedgerEntry->MonetarySource->nameToID('Cash'); $this->Ledger->DoubleEntry->MonetarySource->nameToID('Cash');
} }
} }
@@ -634,7 +719,7 @@ class Account extends AppModel {
//pr(array('pre-save', compact('entry_data'))); //pr(array('pre-save', compact('entry_data')));
// Create it! // Create it!
$new_entry = new LedgerEntry(); $new_entry = new DoubleEntry();
$new_entry->create(); $new_entry->create();
if (!$new_entry->saveAll($entry_data, array('validate'=>false))) { if (!$new_entry->saveAll($entry_data, array('validate'=>false))) {
return array('error' => true); return array('error' => true);
@@ -664,9 +749,9 @@ class Account extends AppModel {
if ($reconcile_set === 'receipt') { if ($reconcile_set === 'receipt') {
$C = new Customer(); $C = new Customer();
$reconciled = $C->reconcileNewLedgerEntry($entry_data['customer_id'], $reconciled = $C->amountWouldReconcile($entry_data['customer_id'],
$this->fundamentalOpposite($dc_type), $this->fundamentalOpposite($dc_type),
$entry_data['amount']); $entry_data['amount']);
/* pr(array("reconcile receipt", */ /* pr(array("reconcile receipt", */
/* compact('reconciled', 'split_transaction', 'transaction_data'))); */ /* compact('reconciled', 'split_transaction', 'transaction_data'))); */
@@ -689,7 +774,7 @@ class Account extends AppModel {
// Payment must debit the Receipt ledger, and credit the A/R ledger // Payment must debit the Receipt ledger, and credit the A/R ledger
// debit: Receipt credit: A/R // debit: Receipt credit: A/R
$ids = $this->postLedgerEntry $ids = $this->postDoubleEntry
($split_transaction, ($split_transaction,
null, null,
array('debit_ledger_id' => $this->currentLedgerID($this->receiptAccountID()), array('debit_ledger_id' => $this->currentLedgerID($this->receiptAccountID()),
@@ -698,9 +783,9 @@ class Account extends AppModel {
'lease_id' => $rec['lease_id'], 'lease_id' => $rec['lease_id'],
'customer_id' => $rec['customer_id'], 'customer_id' => $rec['customer_id'],
), ),
array('debit' => array(array('LedgerEntry' => array('id' => $new_entry->id, array('debit' => array(array('DoubleEntry' => array('id' => $new_entry->id,
'amount' => $rec['applied']))), 'amount' => $rec['applied']))),
'credit' => array(array('LedgerEntry' => array('id' => $rec['id'], 'credit' => array(array('DoubleEntry' => array('id' => $rec['id'],
'amount' => $rec['applied'])))) 'amount' => $rec['applied']))))
); );
// Keep using the same split transaction for all reconciled entries // Keep using the same split transaction for all reconciled entries
@@ -717,19 +802,19 @@ class Account extends AppModel {
if (is_array($reconcile_set)) { if (is_array($reconcile_set)) {
//pr("reconcile_set is array"); //pr("reconcile_set is array");
foreach ($reconcile_set AS $reconcile_entry) { foreach ($reconcile_set AS $reconcile_entry) {
if (!isset($reconcile_entry['LedgerEntry']['id'])) if (!isset($reconcile_entry['DoubleEntry']['id']))
continue; continue;
$amount = $reconcile_entry['LedgerEntry']['amount']; $amount = $reconcile_entry['DoubleEntry']['amount'];
if (!$amount) if (!$amount)
continue; continue;
if ($dc_type == 'debit') { if ($dc_type == 'debit') {
$debit_ledger_entry_id = $new_entry->id; $debit_ledger_entry_id = $new_entry->id;
$credit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id']; $credit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
} }
else { else {
$debit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id']; $debit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
$credit_ledger_entry_id = $new_entry->id; $credit_ledger_entry_id = $new_entry->id;
} }
@@ -749,9 +834,9 @@ class Account extends AppModel {
$ret = array $ret = array
('error' => $err, ('error' => $err,
'id' => $new_entry->data['LedgerEntry']['id'], 'id' => $new_entry->data['DoubleEntry']['id'],
'transaction_id' => $new_entry->data['LedgerEntry']['transaction_id'], 'transaction_id' => $new_entry->data['DoubleEntry']['transaction_id'],
'monetary_source_id' => $new_entry->data['LedgerEntry']['monetary_source_id']); 'monetary_source_id' => $new_entry->data['DoubleEntry']['monetary_source_id']);
if (isset($split_transaction['transaction_id'])) if (isset($split_transaction['transaction_id']))
$ret['split_transaction_id'] = $split_transaction['transaction_id']; $ret['split_transaction_id'] = $split_transaction['transaction_id'];
@@ -783,7 +868,7 @@ class Account extends AppModel {
if ($ledger['total'] == 0) if ($ledger['total'] == 0)
continue; continue;
$ids = $this->postLedgerEntry $ids = $this->postDoubleEntry
($transaction, ($transaction,
null, null,
array('debit_account_id' => $deposit_account_id, array('debit_account_id' => $deposit_account_id,
@@ -795,7 +880,7 @@ class Account extends AppModel {
//pr(compact('ids')); //pr(compact('ids'));
if ($ids['error']) if ($ids['error'])
die("closeAndDeposit : postLedgerEntry returned error!"); die("closeAndDeposit : postDoubleEntry returned error!");
$transaction = array_intersect_key($ids, array('transaction_id'=>1)); $transaction = array_intersect_key($ids, array('transaction_id'=>1));

View File

@@ -86,7 +86,7 @@ class LinkableBehavior extends ModelBehavior {
protected $_defaults = array('type' => 'LEFT'); protected $_defaults = array('type' => 'LEFT');
function pr($lev, $mixed) { function pr($lev, $mixed) {
if ($lev >= 5) if ($lev >= 3)
return; return;
pr($mixed); pr($mixed);
@@ -159,7 +159,7 @@ class LinkableBehavior extends ModelBehavior {
$iterations = Set::normalize($iterator); $iterations = Set::normalize($iterator);
$this->pr(25, $this->pr(25,
array('checkpoint' => 'Iterations', array('checkpoint' => 'Iterations',
compact('iterations'), compact('cont', 'iterator', 'iterations'),
)); ));
foreach ($iterations as $alias => $options) { foreach ($iterations as $alias => $options) {
if (is_null($options)) { if (is_null($options)) {
@@ -246,15 +246,32 @@ class LinkableBehavior extends ModelBehavior {
else else
$associationAlias = $modelAlias; $associationAlias = $modelAlias;
// A couple exceptions before performing a union of
// options and association. Namely, most fields result
// in either/or, but a couple should include BOTH the
// options AND the association settings.
foreach (array('fields', 'conditions') AS $fld) {
if (isset($options[$fld]) && is_array($options[$fld]) &&
isset($association[$fld]) && is_array($association[$fld]))
$options[$fld] = array_merge($options[$fld],
$association[$fld]);
}
// For any option that's not already set, use
// whatever is specified by the assocation.
$options += array_intersect_key($association, $this->_options);
// Replace all instances of the MODEL_ALIAS variable
// tag with the correct model alias.
$this->recursive_array_replace("%{MODEL_ALIAS}", $this->recursive_array_replace("%{MODEL_ALIAS}",
$associationAlias, $associationAlias,
$association['conditions']); $options['conditions']);
$this->pr(15, $this->pr(15,
array('checkpoint' => 'Models Established - Check Associations', array('checkpoint' => 'Models Established - Check Associations',
'primaryModel' => $primaryAlias .' : '. $primaryModel->name, 'primaryModel' => $primaryAlias .' : '. $primaryModel->name,
'foreignModel' => $foreignAlias .' : '. $foreignModel->name, 'foreignModel' => $foreignAlias .' : '. $foreignModel->name,
compact('type', 'association'), compact('type', 'association', 'options'),
)); ));
if ($type === 'hasAndBelongsToMany') { if ($type === 'hasAndBelongsToMany') {
@@ -270,16 +287,29 @@ class LinkableBehavior extends ModelBehavior {
else else
$linkAlias = $Link->alias; $linkAlias = $Link->alias;
// foreignKey and associationForeignKey can refer to either
// the model or the reference, depending on which class
// actually defines the association. Make sure to we're
// using the foreign keys to point to the right class.
if ($associatedThroughReference) {
$modelAFK = 'associationForeignKey';
$referenceAFK = 'foreignKey';
} else {
$modelAFK = 'foreignKey';
$referenceAFK = 'associationForeignKey';
}
$this->pr(17, $this->pr(17,
array('checkpoint' => 'Linking HABTM', array('checkpoint' => 'Linking HABTM',
compact('linkClass', 'linkAlias'), compact('linkClass', 'linkAlias',
'modelAFK', 'referenceAFK'),
)); ));
// Get the foreign key fields (for the link table) directly from // Get the foreign key fields (for the link table) directly from
// the defined model associations, if they exists. This is the // the defined model associations, if they exists. This is the
// users direct specification, and therefore definitive if present. // users direct specification, and therefore definitive if present.
$modelLink = $Link->escapeField($association['foreignKey'], $linkAlias); $modelLink = $Link->escapeField($association[$modelAFK], $linkAlias);
$referenceLink = $Link->escapeField($association['associationForeignKey'], $linkAlias); $referenceLink = $Link->escapeField($association[$referenceAFK], $linkAlias);
// If we haven't figured out the foreign keys, see if there is a // If we haven't figured out the foreign keys, see if there is a
// model for the link table, and if it has the appropriate // model for the link table, and if it has the appropriate
@@ -300,6 +330,12 @@ class LinkableBehavior extends ModelBehavior {
$referenceKey = $Reference->escapeField(null, $referenceAlias); $referenceKey = $Reference->escapeField(null, $referenceAlias);
$modelKey = $_Model->escapeField(null, $modelAlias); $modelKey = $_Model->escapeField(null, $modelAlias);
$this->pr(21,
array('checkpoint' => 'HABTM links/keys',
array(compact('modelLink', 'modelKey'),
compact('referenceLink', 'referenceKey')),
));
// Join the linkage table to our model. We'll use an inner join, // Join the linkage table to our model. We'll use an inner join,
// as the whole purpose of the linkage table is to make this // as the whole purpose of the linkage table is to make this
// connection. As we are embedding this join, the INNER will not // connection. As we are embedding this join, the INNER will not
@@ -345,20 +381,6 @@ class LinkableBehavior extends ModelBehavior {
$this->pr(19, $this->pr(19,
array('checkpoint' => 'Conditions', array('checkpoint' => 'Conditions',
array('options[conditions]' => $options['conditions'],
'association[conditions]' => $association['conditions'],
),
));
// The user may have specified conditions directly in the model
// for this join. Make sure to adhere to those conditions.
if (isset($association['conditions']) && is_array($association['conditions']))
$options['conditions'] = array_merge($options['conditions'], $association['conditions']);
elseif (!empty($association['conditions']))
$options['conditions'][] = $association['conditions'];
$this->pr(19,
array('checkpoint' => 'Conditions2',
array('options[conditions]' => $options['conditions'], array('options[conditions]' => $options['conditions'],
), ),
)); ));
@@ -372,13 +394,16 @@ class LinkableBehavior extends ModelBehavior {
elseif (!empty($options['fields'])) elseif (!empty($options['fields']))
$options['fields'] = $db->fields($_Model, $modelAlias, $options['fields']); $options['fields'] = $db->fields($_Model, $modelAlias, $options['fields']);
$query['fields'] = array_merge($query['fields'], $options['fields'], $query['fields'] = array_merge($query['fields'], $options['fields']);
(empty($association['fields'])
? array() : $db->fields($_Model, $modelAlias, $association['fields'])));
$options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys)); $options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys));
$options = array_intersect_key($options, $optionsKeys); $options = array_intersect_key($options, $optionsKeys);
if (!empty($options[$this->_key])) { if (!empty($options[$this->_key])) {
$this->pr(24,
array('checkpoint' => 'Add new iterator',
'options[this->_key]' => $options[$this->_key],
compact('defaults', 'modelClass', 'modelAlias'),
));
$iterators[] = $options[$this->_key] + $iterators[] = $options[$this->_key] +
array('defaults' => array('defaults' =>
array_merge($defaults, array_merge($defaults,

View File

@@ -19,17 +19,14 @@ class Customer extends AppModel {
'conditions' => 'CurrentLease.close_date IS NULL', 'conditions' => 'CurrentLease.close_date IS NULL',
), ),
'Lease', 'Lease',
'LedgerEntry', 'DoubleEntry',
'ContactsCustomer', 'ContactsCustomer',
'Transaction',
); );
var $hasAndBelongsToMany = array( var $hasAndBelongsToMany = array(
'Contact', 'Contact',
'Transaction' => array(
'joinTable' => 'ledger_entries',
'foreignKey' => 'customer_id',
'associationForeignKey' => 'transaction_id',
),
); );
@@ -87,7 +84,7 @@ class Customer extends AppModel {
$A = new Account(); $A = new Account();
$entries = $A->findLedgerEntries $entries = $A->findLedgerEntries
($A->securityDepositAccountID(), ($A->securityDepositAccountID(),
true, array('LedgerEntry.customer_id' => $id), $link); true, array('DoubleEntry.customer_id' => $id), $link);
/* pr(array('function' => 'Customer::findSecurityDeposits', */ /* pr(array('function' => 'Customer::findSecurityDeposits', */
/* 'args' => compact('id', 'link'), */ /* 'args' => compact('id', 'link'), */
@@ -112,7 +109,7 @@ class Customer extends AppModel {
$unreconciled = $A->findUnreconciledLedgerEntries $unreconciled = $A->findUnreconciledLedgerEntries
($A->accountReceivableAccountID(), ($A->accountReceivableAccountID(),
$fundamental_type, $fundamental_type,
array('LedgerEntry.customer_id' => $id)); array('DoubleEntry.customer_id' => $id));
return $unreconciled; return $unreconciled;
} }
@@ -121,7 +118,7 @@ class Customer extends AppModel {
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: reconcileNewLedgerEntry * function: amountWouldReconcile
* - Returns which ledger entries a new credit/debit would * - Returns which ledger entries a new credit/debit would
* reconcile, and how much. * reconcile, and how much.
* *
@@ -132,18 +129,41 @@ class Customer extends AppModel {
* whatever algorithm is simplest. * whatever algorithm is simplest.
*/ */
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) { function amountWouldReconcile($id, $fundamental_type, $amount) {
$A = new Account(); $A = new Account();
$reconciled = $A->reconcileNewLedgerEntry $reconciled = $A->amountWouldReconcile
($A->accountReceivableAccountID(), ($A->accountReceivableAccountID(),
$fundamental_type, $fundamental_type,
$amount, $amount,
array('LedgerEntry.customer_id' => $id)); array('DoubleEntry.customer_id' => $id));
return $reconciled; return $reconciled;
} }
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcileLedgerEntries
* - Returns which ledger entries a new credit/debit would
* reconcile, and how much.
*
* - REVISIT <AP> 20090617
* This should be subject to different algorithms, such
* as apply to oldest charges first, newest first, to fees
* before rent, etc. Until we get there, I'll hardcode
* whatever algorithm is simplest.
*/
function reconcileLedgerEntries($id) {
$A = new Account();
$reconciled = $A->reconcileLedgerEntries
($A->accountReceivableAccountID(),
array('DoubleEntry.customer_id' => $id));
return $reconciled;
}
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
@@ -274,7 +294,7 @@ class Customer extends AppModel {
$A = new Account(); $A = new Account();
$stats = $A->stats($A->accountReceivableAccountID(), true, $stats = $A->stats($A->accountReceivableAccountID(), true,
array('LedgerEntry.customer_id' => $id)); array('DoubleEntry.customer_id' => $id));
// Pull to the top level and return // Pull to the top level and return
$stats = $stats['Ledger']; $stats = $stats['Ledger'];

View File

@@ -0,0 +1,259 @@
<?php
class DoubleEntry extends AppModel {
var $name = 'DoubleEntry';
var $validate = array(
'id' => array('numeric'),
'transaction_id' => array('numeric'),
'amount' => array('money')
);
var $hasOne = array(
'DebitEntry' => array(
'className' => 'Entry',
'conditions' => array('DebitEntry.crdr' => 'DEBIT'),
),
'CreditEntry' => array(
'className' => 'Entry',
'conditions' => array('CreditEntry.crdr' => 'CREDIT'),
),
);
var $hasMany = array(
'Entry',
);
var $belongsTo = array(
'Transaction',
'Invoice' => array(
'className' => 'Transaction',
'conditions' => array('Invoice.type' => 'INVOICE'),
),
'Receipt' => array(
'className' => 'Transaction',
'conditions' => array('Invoice.type' => 'RECEIPT'),
),
'Customer',
'Lease',
'DebitLedger' => array(
'className' => 'Ledger',
'foreignKey' => 'debit_ledger_id',
),
'CreditLedger' => array(
'className' => 'Ledger',
'foreignKey' => 'credit_ledger_id',
),
'Ledger' => array(
'foreignKey' => false,
// conditions will be used when JOINing tables
// (such as find with LinkableBehavior)
'conditions' => array('OR' =>
array('%{MODEL_ALIAS}.debit_ledger_id = Ledger.id',
'%{MODEL_ALIAS}.credit_ledger_id = Ledger.id')),
// finderQuery will be used when tables are put
// together across several querys, not with JOIN.
// (such as find with ContainableBehavior)
'finderQuery' => 'NOT-IMPLEMENTED',
'counterQuery' => ''
),
);
/**************************************************************************
**************************************************************************
**************************************************************************
* function: conditionEntryAsCreditOrDebit
* - returns the condition necessary to match a set of
* Ledgers to all related LedgerEntries
*/
function conditionEntryAsCreditOrDebit($ledger_ids) {
return array('OR' =>
array(array('debit_ledger_id' => $ledger_ids),
array('credit_ledger_id' => $ledger_ids)));
}
function debitCreditFields($double_name = 'DoubleEntry', $ledger_name = 'Ledger', $sum = true) {
$fields = array
(
($sum ? 'SUM(' : '') .
"IF({$double_name}.debit_ledger_id = {$ledger_name}.id,
{$double_name}.amount, NULL)" .
($sum ? ')' : '') . ' AS debit' . ($sum ? 's' : ''),
($sum ? 'SUM(' : '') .
"IF({$double_name}.credit_ledger_id = {$ledger_name}.id,
{$double_name}.amount, NULL)" .
($sum ? ')' : '') . ' AS credit' . ($sum ? 's' : ''),
($sum ? 'SUM(' : '') .
"IF(Account.type IN ('ASSET', 'EXPENSE'),
IF({$double_name}.debit_ledger_id = {$ledger_name}.id, 1, -1),
IF({$double_name}.credit_ledger_id = {$ledger_name}.id, 1, -1)
) * IF({$double_name}.amount, {$double_name}.amount, 0)" .
($sum ? ')' : '') . ' AS balance',
);
if ($sum)
$fields[] = "COUNT({$double_name}.id) AS entries";
return $fields;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: ledgerContext query helpers
* - Returns parameters necessary to generate a query which
* puts ledger entries into the context of a ledger. Since
* debit/credit depends on the account type, it is required
* as an argument for each function to avoid having to
* query the ledger/account to find it out.
*/
function ledgerContextFields($ledger_id = null, $account_type = null) {
$fields = array('id', 'effective_date',
'lease_id', 'customer_id', 'comment', 'amount');
if (isset($ledger_id)) {
$fields[] = ("IF(DoubleEntry.debit_ledger_id = $ledger_id," .
" DoubleEntry.amount, NULL) AS debit");
$fields[] = ("IF(DoubleEntry.credit_ledger_id = $ledger_id," .
" DoubleEntry.amount, NULL) AS credit");
if (isset($account_type)) {
if (in_array($account_type, array('ASSET', 'EXPENSE')))
$ledger_type = 'debit';
else
$ledger_type = 'credit';
$fields[] = ("(IF(DoubleEntry.{$ledger_type}_ledger_id = $ledger_id," .
" 1, -1) * DoubleEntry.amount) AS balance");
}
}
return $fields;
}
function ledgerContextFields2($ledger_id = null, $account_id = null, $account_type = null) {
$fields = array('id', 'effective_date', 'comment', 'amount');
if (isset($ledger_id)) {
$fields[] = ("IF(DoubleEntry.debit_ledger_id = $ledger_id," .
" SUM(DoubleEntry.amount), NULL) AS debit");
$fields[] = ("IF(DoubleEntry.credit_ledger_id = $ledger_id," .
" SUM(DoubleEntry.amount), NULL) AS credit");
if (isset($account_id) || isset($account_type)) {
$Account = new Account();
$account_ftype = $Account->fundamentalType($account_id ? $account_id : $account_type);
$fields[] = ("(IF(DoubleEntry.{$account_ftype}_ledger_id = $ledger_id," .
" 1, -1) * SUM(DoubleEntry.amount)) AS balance");
}
}
elseif (isset($account_id)) {
$fields[] = ("IF(DebitLedger.account_id = $account_id," .
" SUM(DoubleEntry.amount), NULL) AS debit");
$fields[] = ("IF(CreditLedger.account_id = $account_id," .
" SUM(DoubleEntry.amount), NULL) AS credit");
$Account = new Account();
$account_ftype = ucfirst($Account->fundamentalType($account_id));
$fields[] = ("(IF({$account_ftype}Ledger.account_id = $account_id," .
" 1, -1) * SUM(DoubleEntry.amount)) AS balance");
}
return $fields;
}
function ledgerContextConditions($ledger_id, $account_type) {
if (isset($ledger_id)) {
return array
('OR' =>
array(array('DoubleEntry.debit_ledger_id' => $ledger_id),
array('DoubleEntry.credit_ledger_id' => $ledger_id)),
);
}
return array();
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: findInLedgerContext
* - Returns an array of ledger entries that belong to a given ledger.
* There is extra logic to also figure out whether the double_entry
* amount is either a credit, or a debit, depending on how it was
* written into the ledger, as well as whether the amount increases or
* decreases the balance depending on the particular account type of
* the ledger.
*/
function findInLedgerContext($ledger_id, $account_type, $cond = null, $link = null) {
if (!isset($link))
$link = array('Transaction');
if (!isset($cond))
$cond = array();
$fields = $this->ledgerContextFields($ledger_id, $account_type);
$cond[] = $this->ledgerContextConditions($ledger_id, $account_type);
$order = array('Transaction.stamp');
$entries = $this->find
('all',
array('link' => $link,
'fields' => $fields,
'conditions' => $cond,
'order' => $order,
));
return $entries;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: stats
* - Returns summary data from the requested double entry
*/
function stats($id) {
/* $query = array */
/* ( */
/* 'fields' => array("SUM(Reconciliation.amount) AS 'reconciled'"), */
/* 'conditions' => array(isset($cond) ? $cond : array(), */
/* array('DoubleEntry.id' => $id)), */
/* 'group' => 'DoubleEntry.id', */
/* ); */
/* // Get the applied amounts on the debit side */
/* $query['link'] = */
/* array('DebitReconciliationDoubleEntry' => array('alias' => 'DRLE', 'DRLETransaction' => array('class' => 'Transaction'))); */
/* $tmpstats = $this->find('first', $query); */
/* $stats['debit_amount_reconciled'] = $tmpstats[0]['reconciled']; */
/* // Get the applied amounts on the credit side */
/* $query['link'] = */
/* array('CreditReconciliationDoubleEntry' => array('alias' => 'CRLE', 'CRLETransaction' => array('class' => 'Transaction'))); */
/* $tmpstats = $this->find('first', $query); */
/* $stats['credit_amount_reconciled'] = $tmpstats[0]['reconciled']; */
$stats['debit']['amount_reconciled'] = 0;
$stats['credit']['amount_reconciled'] = 0;
return $stats;
}
}

578
site/models/entry.php Normal file
View File

@@ -0,0 +1,578 @@
<?php
class Entry extends AppModel {
var $belongsTo = array(
'Account',
'DoubleEntry',
);
var $hasAndBelongsToMany = array(
// The Payments that match THIS Charge (if it is one)
'Payment' => array(
'className' => 'Entry',
'joinTable' => 'charges_payments',
'linkalias' => 'AppliedPayment',
'foreignKey' => 'charge_entry_id',
'associationForeignKey' => 'payment_entry_id',
),
// The Charges that match THIS Payment (if it is one)
'Charge' => array(
'className' => 'Entry',
'joinTable' => 'charges_payments',
'linkalias' => 'AppliedCharge',
'foreignKey' => 'payment_entry_id',
'associationForeignKey' => 'charge_entry_id',
),
// The Debits of this same account matching THIS Credit (if it is one)
'Debit' => array(
'className' => 'Entry',
'joinTable' => 'reconciliations',
'linkalias' => 'AppliedDebit',
'foreignKey' => 'credit_entry_id',
'associationForeignKey' => 'debit_entry_id',
),
// The Credits of this same account matching THIS Debit (if it is one)
'Credit' => array(
'className' => 'Entry',
'joinTable' => 'reconciliations',
'linkalias' => 'AppliedCredit',
'foreignKey' => 'debit_entry_id',
'associationForeignKey' => 'credit_entry_id',
),
/* // The Entries of this same account matching THIS Entry */
/* 'REntry' => array( */
/* 'className' => 'Entry', */
/* 'joinTable' => 'reconciliations', */
/* //'linkalias' => 'AppliedCredit', */
/* 'foreignKey' => false, */
/* 'associationForeignKey' => false, */
/* 'conditions' => array( */
/* "Reconciliation.credit_entry_id */
/* = IF(Entry.crdr = 'CREDIT', Entry.id, REntry.id)", */
/* "Reconciliation.debit_entry_id */
/* = IF(Entry.crdr = 'DEBIT', Entry.id, REntry.id)" */
/* ), */
/* ), */
);
/**************************************************************************
**************************************************************************
**************************************************************************
* function: addCharge
* - Adds a new charge
*/
function addCharge($data, $transaction, $customer_id, $lease_id = null) {
// Create some models for convenience
$A = new Account();
// Assume this will succeed
$ret = true;
// Establish the key charge parameters
$charge = array_intersect_key($data, array('stamp'=>1, 'amount'=>1, 'account_id'=>1));
$charge['customer_id'] = $customer_id;
$charge['lease_id'] = $lease_id;
$charge['type'] = 'CHARGE';
$ids = $this->Entry->Ledger->Account->postDoubleEntry
($transaction,
$charge,
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
) + $data
);
if ($ids['error'])
$ret = false;
return $ids['charge_id'];
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: addPayment
* - Adds a new payment
*/
function addPayment($data, $transaction, $customer_id, $lease_id = null) {
// Create some models for convenience
$A = new Account();
// Assume this will succeed
$ret = true;
// Establish the key payment parameters
$payment = array_intersect_key($data, array('stamp'=>1, 'name'=>1, 'monetary_type'=>1,
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1,
'amount'=>1, 'account_id'=>1));
$payment['customer_id'] = $customer_id;
$payment['type'] = 'PAYMENT';
$ids = $this->Entry->Ledger->Account->postDoubleEntry
($transaction,
$payment,
array('debit_ledger_id' => $A->currentLedgerID($data['account_id']),
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID())
) + $data
);
if ($ids['error'])
$ret = false;
return $ids['payment_id'];
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: findInLedgerContext
* - Returns an array of ledger entries that belong to a given ledger.
* There is extra logic to also figure out whether the ledger_entry
* amount is either a credit, or a debit, depending on how it was
* written into the ledger, as well as whether the amount increases or
* decreases the balance depending on the particular account type of
* the ledger.
*/
function findInLedgerContext($ledger_id, $account_type, $cond = null, $link = null) {
if (!isset($link))
$link = array('Transaction');
if (!isset($cond))
$cond = array();
$fields = $this->ledgerContextFields($ledger_id, $account_type);
$cond[] = $this->ledgerContextConditions($ledger_id, $account_type);
$order = array('Transaction.stamp');
$entries = $this->find
('all',
array('link' => $link,
'fields' => $fields,
'conditions' => $cond,
'order' => $order,
));
return $entries;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reverse
* - Reverses the charges
*
* SAMPLE MOVE IN w/ PRE PAYMENT
* DEPOSIT RENT A/R RECEIPT CHECK PETTY BANK
* ------- ------- ------- ------- ------- ------- -------
* |25 | 25| | | | |
* | |20 20| | | | |
* | |20 20| | | | |
* | |20 20| | | | |
* | | |25 25| | | |
* | | |20 20| | | |
* | | |20 20| | | |
* | | |20 20| | | |
* | | | |85 85| | |
* | | | | |85 | 85|
* MOVE OUT and REFUND FINAL MONTH
* DEPOSIT RENT C/P RECEIPT CHECK PETTY BANK
* ------- ------- ------- ------- ------- ------- -------
* 25| | |25 | | | | t20 e20a
* | 20| |20 | | | | t20 e20b
* -ONE REFUND CHECK-
* | | 25| |25 | | | t30 e30a
* | | 20| |20 | | | t30 e30b
* | | | 45| | | |45 t40 e40
* -OR MULTIPLE-
* | | 15| |15 | | | t50a e50a
* | | | 15| | |15 | t60a e60a
* | | 30| |30 | | | t50b e50b
* | | | 30| | | |30 t60b e60b
* | | | | | | |
OPTION 1
* |-25 | -25| | | | |
* | |-20 -20| | | | |
* | | |-25 -25| | | |
* | | |-20 -20| | | |
OPTION 2
* |-25 | | -25| | | |
* | |-20 | -20| | | |
* | | | |-15 | -15| |
* | | | |-30 | | -30|
* | | | | | | |
*
*/
function reverse($ledger_entries, $stamp = null) {
pr(array('Entry::reverse',
compact('ledger_entries', 'stamp')));
// If the user only wants to reverse one ID, we'll allow it
if (!is_array($ledger_entries))
$ledger_entries = $this->find
('all', array
('contain' => false,
'conditions' => array('Entry.id' => $ledger_entries)));
$A = new Account();
$ar_account_id = $A->accountReceivableAccountID();
$receipt_account_id = $A->receiptAccountID();
$transaction_id = null;
foreach ($ledger_entries AS $entry) {
$entry = $entry['Entry'];
$amount = -1*$entry['amount'];
if (isset($entry['credit_account_id']))
$refund_account_id = $entry['credit_account_id'];
elseif (isset($entry['CreditLedger']['Account']['id']))
$refund_account_id = $entry['CreditLedger']['Account']['id'];
elseif (isset($entry['credit_ledger_id']))
$refund_account_id = $this->Ledger->accountID($entry['credit_ledger_id']);
else
return null;
// post new refund in the income account
$ids = $A->postEntry
(array('transaction_id' => $transaction_id),
null,
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),
'credit_ledger_id' => $A->currentLedgerID($refund_account_id),
'effective_date' => $entry['effective_date'],
'through_date' => $entry['through_date'],
'amount' => $amount,
'lease_id' => $entry['lease_id'],
'customer_id' => $entry['customer_id'],
'comment' => "Refund; Entry #{$entry['id']}",
),
array('debit' => array
(array('Entry' =>
array('id' => $entry['id'],
'amount' => $amount))),
)
);
if ($ids['error'])
return null;
$transaction_id = $ids['transaction_id'];
pr(array('checkpoint' => 'Posted Refund Ledger Entry',
compact('ids', 'amount', 'refund_account_id', 'ar_account_id')));
}
return true;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcileConditions
* - Returns entries which reconcile, or match, the set of entries
* requested through the $cond argument.
*/
function reconcilingQuery($double_name = 'DoubleEntry', $sum = false) {
$applied = array();
foreach (array('Payment', 'Charge') AS $pc) {
$applied[$pc] = "COALESCE(Applied{$pc}.amount,0)";
if ($sum)
$applied[$pc] = "SUM({$applied[$pc]})";
}
return array
('fields' => array("IF(Entry.type = 'CHARGE'," .
" {$applied['Payment']}, {$applied['Charge']}) AS 'applied'",
"{$double_name}.amount - IF(Entry.type = 'CHARGE'," .
" {$applied['Payment']}, {$applied['Charge']}) AS 'balance'",
),
'conditions' => array(),
);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: matchingEntries
* - Returns entries which reconcile, or match, the set of entries
* requested through the $cond argument.
*/
function matchingEntries($cond, $group, $strip_rec = false, $strip_unrec = false) {
$rquery = $this->reconcilingQuery('DoubleEntry', $group);
$fields = array_merge(array('Entry.*'), $rquery['fields']);
$cond = array_merge($cond, $rquery['conditions']);
$reconciled = $this->find
('all', array
('link' => array('DoubleEntry', 'Account',
'Charge' => array('fields' => array('Charge.*', 'AppliedCharge.*')),
'Payment' => array('fields' => array('Payment.*', 'AppliedPayment.*')),
),
'fields' => $fields,
'group' => $group,
'conditions' => $cond,
));
foreach ($reconciled AS $i => &$entry) {
$entry['Entry'] += $entry[0];
unset($entry[0]);
// Since HAVING isn't a builtin feature of CakePHP,
// we'll have to post-process to get the desired entries
if ($entry['Entry']['balance'] == 0) {
if ($strip_rec)
unset($reconciled[$i]);
}
else {
if ($strip_unrec)
unset($reconciled[$i]);
}
}
//pr(compact('reconciled'));
return $reconciled;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconciledEntries
* - Returns the list of entries that have been reconciled (if $rec),
* or not fully reconciled (if $rec is false), along with the amount
* that has already been applied towards the entry as well as the
* remaining balance.
*/
function reconciledEntries1($id, $rec = true, $cond = null) {
$cond[] = array('Entry.id' => $id);
//return $this->matchingEntries($cond, array('Entry.id'), !$rec, $rec);
return $this->matchingEntries($cond, array('Entry.id'), false, false);
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcilingEntries
* - Returns a list of entries that reconcile against the given entry.
* (such as payments towards a charge).
*/
function reconciledEntries($id, $cond = null) {
if (!isset($cond))
$cond = array();
$cond[] = array('Entry.id' => $id);
$entry = $this->find('first', array('recursive' => -1));
$entry = $entry['Entry'];
$contain = array();
if ($entry['type'] == 'CHARGE')
$contain['Payment'] = array('fields' => array('Payment.*', 'ChargesPayment.amount'));
if ($entry['type'] == 'PAYMENT')
$contain['Charge'] = array('fields' => array('Charge.*', 'ChargesPayment.amount'));
if ($entry['crdr'] == 'DEBIT')
$contain['Credit'] = array('fields' => array('Credit.*', 'Reconciliation.amount'));
if ($entry['crdr'] == 'CREDIT')
$contain['Debit'] = array('fields' => array('Debit.*', 'Reconciliation.amount'));
//pr(array('reconciledEntries', compact('entry', 'contain')));
$result = array();
$result['entries'] = $this->find
('first', array
(
'contain' => $contain,
/* 'zcontain' => array(//'DoubleEntry' => array('fields' => array('amount')), */
/* 'REntry' => array('fields' => array('Reconciliation.amount'), */
/* 'DoubleEntry'), */
/* ), */
/* 'zlink' => array(//'DoubleEntry' => array('fields' => array('amount')), */
/* 'REntry' => array('fields' => array('REntry.*', 'Reconciliation.amount'), */
/* 'DoubleEntry'), */
/* ), */
/* 'zlink' => array(//'DoubleEntry' => array('fields' => array('amount')), */
/* 'Debit' => array('fields' => array('AppliedDebit.amount'), */
/* 'DoubleEntry' => array('alias' => 'DebitDoubleEntry')), */
/* 'Credit' => array('fields' => array('AppliedCredit.amount'), */
/* 'DoubleEntry' => array('alias' => 'CreditDoubleEntry')), */
/* ), */
'fields' => array('id'),
'conditions' => $cond,
));
unset($result['entries']['Entry']);
$result['stats'] = $this->stats($id);
return $result;
/* $balance = $this->find */
/* ('first', array */
/* ('link' => array */
/* ( */
/* "Charge" => array */
/* ('fields' => array("SUM(COALESCE(ChargesPayment.amount,0)) AS 'reconciling_charges'")), */
/* "Payment" => array */
/* ('fields' => array("SUM(COALESCE(ChargesPayment.amount,0)) AS 'reconciling_payments'")), */
/* ), */
/* 'fields' => array('Entry.amount'), */
/* 'group' => array('Entry.id'); */
/* 'conditions' => $cond, */
/* )); */
/* pr(compact('balance')); */
// pull up, since there should only be one
//$reconciling = $reconciling[0];
// Add the calculated fields to Entry
/* $reconciling['applied'] = 0; */
/* $reconciling['balance'] = $reconciling[0]['Entry']['amount']; */
/* foreach ($reconciling AS $i => $entry) { */
/* $reconciling['applied'] += $entry[0]['applied']; */
/* unset($reconciling[$i]['Entry']); */
/* } */
/* $reconciling['balance'] -= $reconciling['applied']; */
/* pr(compact('reconciling')); */
/* return $reconciling; */
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: stats
* - Returns summary data from the requested ledger entry
*/
function stats($id, $cond = null) {
if (!isset($cond))
$cond = array();
$cond[] = array('Entry.id' => $id);
$entry = $this->find('first', array('contain' => array('DoubleEntry.amount'),
'fields' => array('Entry.type', 'Entry.crdr')));
$entry['Entry'] += $entry['DoubleEntry'];
$entry = $entry['Entry'];
$stats = array();
foreach(array('charge', 'payment', 'debit', 'credit') AS $rtype) {
$Rtype = ucfirst($rtype);
if (($rtype == 'charge' && $entry['type'] == 'PAYMENT') ||
($rtype == 'payment' && $entry['type'] == 'CHARGE') ||
($rtype == 'debit' && $entry['crdr'] == 'CREDIT') ||
($rtype == 'credit' && $entry['crdr'] == 'DEBIT')) {
$result = $this->find
('first', array
('link' =>
array($Rtype =>
array('fields' => array("SUM(Applied{$Rtype}.amount) AS applied"))),
'fields' => array(),
'conditions' => $cond,
'group' => 'Entry.id',
));
//pr(compact('Rtype', 'result'));
$sumfld = $Rtype;
$stats[$sumfld] = $result[0];
if (!isset($stats[$sumfld]['applied']))
$stats[$sumfld]['applied'] = 0;
$stats[$sumfld]['unapplied'] = $entry['amount'] - $stats[$sumfld]['applied'];
}
}
return $stats;
/* $result = $this->find */
/* ('first', array */
/* ('link' => array('DoubleEntry' => array('fields' => array('amount')), */
/* 'Credit' => array('fields' => array()), */
/* 'Debit' => array('fields' => array())), */
/* 'fields' => array('Entry.crdr', */
/* "SUM(AppliedDebit.amount) AS 'applied_debits'", */
/* "SUM(AppliedCredit.amount) AS 'applied_credits'"), */
/* 'conditions' => $cond, */
/* 'group' => 'Entry.id', */
/* )); */
//pr(compact('result'));
$stats = array();
if ($result['Entry']['crdr'] == 'DEBIT') {
if ($result[0]['applied_debits'])
die('INCONSISTENT DATABASE ENTRIES');
$stats['applied'] = $result[0]['applied_credits'];
}
elseif ($result['Entry']['crdr'] == 'CREDIT') {
if ($result[0]['applied_credits'])
die('INCONSISTENT DATABASE ENTRIES');
$stats['applied'] = $result[0]['applied_debits'];
}
if (!isset($stats['applied']))
$stats['applied'] = 0;
$stats['unapplied'] = $result['DoubleEntry']['amount'] - $stats['applied'];
/* $reconciled = $this->find */
/* ('all', array */
/* ('link' => array */
/* ( */
/* "Charge" => array */
/* ('fields' => array */
/* ('id', */
/* "COALESCE(SUM(ChargesPayment.amount),0) AS 'reconciled'", */
/* "Entry.amount - COALESCE(SUM(ChargesPayment.amount),0) AS 'balance'", */
/* ), */
/* ), */
/* "Payment" => array */
/* ('fields' => array */
/* ('id', */
/* "COALESCE(SUM(ChargesPayment.amount),0) AS 'reconciled'", */
/* "Entry.amount - COALESCE(SUM(ChargesPayment.amount),0) AS 'balance'", */
/* ), */
/* ), */
/* ), */
/* 'conditions' => array(isset($cond) ? $cond : array(), */
/* array('Entry.id' => $id)), */
/* 'group' => 'Entry.id', */
/* )); */
return $stats;
}
}

View File

@@ -1,27 +1,6 @@
<?php <?php
class Lease extends AppModel { class Lease extends AppModel {
var $name = 'Lease';
var $validate = array(
'id' => array('numeric'),
'number' => array('alphanumeric'),
'lease_type_id' => array('numeric'),
'unit_id' => array('numeric'),
'late_schedule_id' => array('numeric'),
'lease_date' => array('date'),
'movein_planned_date' => array('date'),
'movein_date' => array('date'),
'moveout_date' => array('date'),
'moveout_planned_date' => array('date'),
'notice_given_date' => array('date'),
'notice_received_date' => array('date'),
'close_date' => array('date'),
'deposit' => array('money'),
'rent' => array('money'),
'next_rent' => array('money'),
'next_rent_date' => array('date')
);
var $belongsTo = array( var $belongsTo = array(
'LeaseType', 'LeaseType',
'Unit', 'Unit',
@@ -30,7 +9,7 @@ class Lease extends AppModel {
); );
var $hasMany = array( var $hasMany = array(
'LedgerEntry', 'DoubleEntry',
); );
@@ -60,7 +39,7 @@ class Lease extends AppModel {
if (!isset($cond)) if (!isset($cond))
$cond = array(); $cond = array();
$cond[] = array('LedgerEntry.lease_id' => $id); $cond[] = array('DoubleEntry.lease_id' => $id);
$A = new Account(); $A = new Account();
$entries = $A->findLedgerEntries($this->accountId($id), $entries = $A->findLedgerEntries($this->accountId($id),
@@ -89,7 +68,7 @@ class Lease extends AppModel {
$A = new Account(); $A = new Account();
$entries = $A->findLedgerEntries $entries = $A->findLedgerEntries
($A->securityDepositAccountID(), ($A->securityDepositAccountID(),
true, array('LedgerEntry.lease_id' => $id), $link); true, array('DoubleEntry.lease_id' => $id), $link);
/* pr(array('function' => 'Lease::findSecurityDeposits', */ /* pr(array('function' => 'Lease::findSecurityDeposits', */
/* 'args' => compact('id', 'link'), */ /* 'args' => compact('id', 'link'), */
@@ -100,42 +79,6 @@ class Lease extends AppModel {
} }
/**************************************************************************
**************************************************************************
**************************************************************************
* function: findUnreconciledLedgerEntries
* - Returns ledger entries that are not yet reconciled
* (such as charges not paid).
*/
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
$A = new Account();
return $A->findUnreconciledLedgerEntries
($this->accountId($id), $fundamental_type, array('LedgerEntry.lease_id' => $id));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcileNewLedgerEntry
* - Returns which ledger entries a new credit/debit would
* reconcile, and how much.
*
* - REVISIT <AP> 20090617
* This should be subject to different algorithms, such
* as apply to oldest charges first, newest first, to fees
* before rent, etc. Until we get there, I'll hardcode
* whatever algorithm is simplest.
*/
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
$A = new Account();
return $A->reconcileNewLedgerEntry
($this->accountId($id), $fundamental_type, $amount, array('LedgerEntry.lease_id' => $id));
}
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
@@ -153,7 +96,7 @@ class Lease extends AppModel {
('all', ('all',
array('link' => array('link' =>
array(// Models array(// Models
'LedgerEntry' => array 'DoubleEntry' => array
('Ledger' => array ('Ledger' => array
('fields' => array(), ('fields' => array(),
'Account' => array 'Account' => array
@@ -161,12 +104,12 @@ class Lease extends AppModel {
'Ledger' => array 'Ledger' => array
('alias' => 'Lx', ('alias' => 'Lx',
'fields' => array(), 'fields' => array(),
'LedgerEntry' => array 'DoubleEntry' => array
('alias' => 'LEx', ('alias' => 'LEx',
'fields' => array(), 'fields' => array(),
'conditions' => array 'conditions' => array
('LEx.effective_date = DATE_ADD(LedgerEntry.through_date, INTERVAL 1 day)', ('LEx.effective_date = DATE_ADD(DoubleEntry.through_date, INTERVAL 1 day)',
'LEx.lease_id = LedgerEntry.lease_id', 'LEx.lease_id = DoubleEntry.lease_id',
) )
), ),
), ),
@@ -219,7 +162,7 @@ class Lease extends AppModel {
return false; return false;
if (count($entries) != 1) if (count($entries) != 1)
return null; return null;
return $entries[0]['LedgerEntry']['through_date']; return $entries[0]['DoubleEntry']['through_date'];
} }
@@ -234,8 +177,8 @@ class Lease extends AppModel {
// Income / Receipt / Money // Income / Receipt / Money
// debit: A/R credit: Income <-- this entry // debit: A/R credit: Income <-- this entry
// debit: Receipt credit: A/R <-- ReceiptLedgerEntry, below // debit: Receipt credit: A/R <-- ReceiptDoubleEntry, below
// debit: Money credit: Receipt <-- MoneyLedgerEntry, below // debit: Money credit: Receipt <-- MoneyDoubleEntry, below
$query = array $query = array
('link' => array ('link' => array
@@ -250,43 +193,43 @@ class Lease extends AppModel {
// We're searching for the Receipt<->A/R entries, // We're searching for the Receipt<->A/R entries,
// which are debits on the A/R account. Find the // which are debits on the A/R account. Find the
// reconciling entries to that A/R debit. // reconciling entries to that A/R debit.
'DebitReconciliationLedgerEntry' => 'DebitReconciliationDoubleEntry' =>
array('alias' => 'ReceiptLedgerEntry', array('alias' => 'ReceiptDoubleEntry',
'fields' => array(), 'fields' => array(),
// Finally, the Money (Cash/Check/etc) Entry is the one // Finally, the Money (Cash/Check/etc) Entry is the one
// which reconciles our ReceiptLedgerEntry debit // which reconciles our ReceiptDoubleEntry debit
'DebitReconciliationLedgerEntry' => 'DebitReconciliationDoubleEntry' =>
array('alias' => 'MoneyLedgerEntry', array('alias' => 'MoneyDoubleEntry',
'linkalias' => 'MoneyLedgerEntryR', 'linkalias' => 'MoneyDoubleEntryR',
'fields' => array('SUM(COALESCE(MoneyLedgerEntryR.amount,0)) AS paid'), 'fields' => array('SUM(COALESCE(MoneyDoubleEntryR.amount,0)) AS paid'),
), ),
), ),
), ),
'fields' => array('LedgerEntry.amount', 'fields' => array('DoubleEntry.amount',
'DATE_SUB(LedgerEntry.effective_date, INTERVAL 1 DAY) AS paid_through', 'DATE_SUB(DoubleEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
), ),
'group' => 'LedgerEntry.id HAVING paid <> LedgerEntry.amount', 'group' => 'DoubleEntry.id HAVING paid <> DoubleEntry.amount',
'conditions' => array(array('LedgerEntry.lease_id' => $id), 'conditions' => array(array('DoubleEntry.lease_id' => $id),
array('Account.id' => $this->LedgerEntry->Ledger->Account->rentAccountID()), array('Account.id' => $this->DoubleEntry->Ledger->Account->rentAccountID()),
), ),
'order' => array('LedgerEntry.effective_date', 'order' => array('DoubleEntry.effective_date',
), ),
); );
$rent = $this->LedgerEntry->find('first', $query); $rent = $this->DoubleEntry->find('first', $query);
if ($rent) if ($rent)
return $rent[0]['paid_through']; return $rent[0]['paid_through'];
$query['fields'] = 'LedgerEntry.through_date'; $query['fields'] = 'DoubleEntry.through_date';
$query['order'] = 'LedgerEntry.through_date DESC'; $query['order'] = 'DoubleEntry.through_date DESC';
$query['group'] = 'LedgerEntry.id'; $query['group'] = 'DoubleEntry.id';
$rent = $this->LedgerEntry->find('first', $query); $rent = $this->DoubleEntry->find('first', $query);
if ($rent) if ($rent)
return $rent['LedgerEntry']['through_date']; return $rent['DoubleEntry']['through_date'];
return null; return null;
} }
@@ -495,7 +438,7 @@ class Lease extends AppModel {
$A = new Account(); $A = new Account();
$stats = $A->stats($A->accountReceivableAccountID(), true, $stats = $A->stats($A->accountReceivableAccountID(), true,
array('LedgerEntry.lease_id' => $id)); array('DoubleEntry.lease_id' => $id));
// Pull to the top level and return // Pull to the top level and return
$stats = $stats['Ledger']; $stats = $stats['Ledger'];

View File

@@ -1,12 +1,6 @@
<?php <?php
class Ledger extends AppModel { class Ledger extends AppModel {
var $name = 'Ledger';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
);
var $belongsTo = array( var $belongsTo = array(
'Account', 'Account',
'PriorLedger' => array('className' => 'Ledger'), 'PriorLedger' => array('className' => 'Ledger'),
@@ -14,32 +8,33 @@ class Ledger extends AppModel {
); );
var $hasMany = array( var $hasMany = array(
'LedgerEntry' => array( 'DoubleEntry' => array(
'foreignKey' => false, 'foreignKey' => false,
// conditions will be used when JOINing tables // conditions will be used when JOINing tables
// (such as find with LinkableBehavior) // (such as find with LinkableBehavior)
'conditions' => array('OR' => 'conditions' => array('OR' =>
array('LedgerEntry.debit_ledger_id = %{MODEL_ALIAS}.id', array('DoubleEntry.debit_ledger_id = %{MODEL_ALIAS}.id',
'LedgerEntry.credit_ledger_id = %{MODEL_ALIAS}.id')), 'DoubleEntry.credit_ledger_id = %{MODEL_ALIAS}.id')),
// finderQuery will be used when tables are put // finderQuery will be used when tables are put
// together across several querys, not with JOIN. // together across several querys, not with JOIN.
// (such as find with ContainableBehavior) // (such as find with ContainableBehavior)
'finderQuery' => 'SELECT `LedgerEntry`.* 'finderQuery' => 'SELECT `DoubleEntry`.*
FROM pmgr_ledger_entries AS `LedgerEntry` FROM pmgr_double_entries AS `DoubleEntry`
WHERE LedgerEntry.debit_ledger_id = ({$__cakeID__$}) WHERE DoubleEntry.debit_ledger_id = ({$__cakeID__$})
OR LedgerEntry.credit_ledger_id = ({$__cakeID__$})', OR DoubleEntry.credit_ledger_id = ({$__cakeID__$})',
'counterQuery' => '' 'counterQuery' => ''
), ),
'DebitLedgerEntry' => array( 'DebitLedgerEntry' => array(
'className' => 'LedgerEntry', 'className' => 'DoubleEntry',
'foreignKey' => 'debit_ledger_id', 'foreignKey' => 'debit_ledger_id',
'dependent' => false, 'dependent' => false,
), ),
'CreditLedgerEntry' => array( 'CreditLedgerEntry' => array(
'className' => 'LedgerEntry', 'className' => 'DoubleEntry',
'foreignKey' => 'credit_ledger_id', 'foreignKey' => 'credit_ledger_id',
'dependent' => false, 'dependent' => false,
), ),
@@ -124,7 +119,7 @@ class Ledger extends AppModel {
'comment' => "Ledger Balance Forward", 'comment' => "Ledger Balance Forward",
); );
$carry_entry = new LedgerEntry(); $carry_entry = new DoubleEntry();
$carry_entry->create(); $carry_entry->create();
if (!$carry_entry->save($carry_entry_data, false)) { if (!$carry_entry->save($carry_entry_data, false)) {
return null; return null;
@@ -137,11 +132,11 @@ class Ledger extends AppModel {
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: findLedgerEntries * function: ledgerEntries
* - Returns an array of ledger entries that belong to a given * - Returns an array of ledger entries that belong to a given
* ledger. There is extra work done... see the LedgerEntry model. * ledger. There is extra work done... see the DoubleEntry model.
*/ */
function findLedgerEntries($id, $account_type = null, $cond = null, $link = null) { function ledgerEntries($id, $account_type = null, $cond = null, $link = null) {
/* pr(array('function' => 'Ledger::findLedgerEntries', */ /* pr(array('function' => 'Ledger::findLedgerEntries', */
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */ /* 'args' => compact('id', 'account_type', 'cond', 'link'), */
/* )); */ /* )); */
@@ -177,7 +172,7 @@ class Ledger extends AppModel {
'credit' => $stats['credits'], 'credit' => $stats['credits'],
'balance' => $stats['balance']), 'balance' => $stats['balance']),
'LedgerEntry' => array('id' => null, 'DoubleEntry' => array('id' => null,
//'comment' => "Balance Forward from $date"), //'comment' => "Balance Forward from $date"),
'comment' => "-- SUMMARY OF EXCLUDED ENTRIES --"), 'comment' => "-- SUMMARY OF EXCLUDED ENTRIES --"),
@@ -188,7 +183,7 @@ class Ledger extends AppModel {
)); ));
} }
$entries = $this->LedgerEntry->findInLedgerContext($id, $account_type, $cond, $link); $entries = $this->DoubleEntry->findInLedgerContext($id, $account_type, $cond, $link);
/* pr(array('function' => 'Ledger::findLedgerEntries', */ /* pr(array('function' => 'Ledger::findLedgerEntries', */
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */ /* 'args' => compact('id', 'account_type', 'cond', 'link'), */
/* 'vars' => compact('ledger'), */ /* 'vars' => compact('ledger'), */
@@ -214,23 +209,23 @@ class Ledger extends AppModel {
('link' => ('link' =>
array(// Models array(// Models
'Account' => array('fields' => array()), 'Account' => array('fields' => array()),
//'LedgerEntry' => array('fields' => array()), //'DoubleEntry' => array('fields' => array()),
'LedgerEntry' => 'DoubleEntry' =>
array('fields' => array(), array('fields' => array(),
'Transaction' => array('fields' => array('stamp')), 'Transaction' => array('fields' => array('stamp')),
), ),
), ),
'fields' => 'fields' =>
array("SUM(IF(LedgerEntry.debit_ledger_id = Ledger.id, array("SUM(IF(DoubleEntry.debit_ledger_id = Ledger.id,
LedgerEntry.amount, NULL)) AS debits", DoubleEntry.amount, NULL)) AS debits",
"SUM(IF(LedgerEntry.credit_ledger_id = Ledger.id, "SUM(IF(DoubleEntry.credit_ledger_id = Ledger.id,
LedgerEntry.amount, NULL)) AS credits", DoubleEntry.amount, NULL)) AS credits",
"SUM(IF(Account.type IN ('ASSET', 'EXPENSE'), "SUM(IF(Account.type IN ('ASSET', 'EXPENSE'),
IF(LedgerEntry.debit_ledger_id = Ledger.id, 1, -1), IF(DoubleEntry.debit_ledger_id = Ledger.id, 1, -1),
IF(LedgerEntry.credit_ledger_id = Ledger.id, 1, -1) IF(DoubleEntry.credit_ledger_id = Ledger.id, 1, -1)
) * IF(LedgerEntry.amount, LedgerEntry.amount, 0) ) * IF(DoubleEntry.amount, DoubleEntry.amount, 0)
) AS balance", ) AS balance",
"COUNT(LedgerEntry.id) AS entries"), "COUNT(DoubleEntry.id) AS entries"),
'conditions' => $cond, 'conditions' => $cond,
'group' => 'Ledger.id', 'group' => 'Ledger.id',
)); ));

View File

@@ -1,525 +0,0 @@
<?php
class LedgerEntry extends AppModel {
var $name = 'LedgerEntry';
var $validate = array(
'id' => array('numeric'),
'transaction_id' => array('numeric'),
'amount' => array('money')
);
var $hasMany = array(
'DebitReconciliation' => array(
'className' => 'Reconciliation',
'foreignKey' => 'debit_ledger_entry_id',
),
'CreditReconciliation' => array(
'className' => 'Reconciliation',
'foreignKey' => 'credit_ledger_entry_id',
),
);
var $belongsTo = array(
'MonetarySource',
'Transaction',
'Customer',
'Lease',
'DebitLedger' => array(
'className' => 'Ledger',
'foreignKey' => 'debit_ledger_id',
),
'CreditLedger' => array(
'className' => 'Ledger',
'foreignKey' => 'credit_ledger_id',
),
'Ledger' => array(
'foreignKey' => false,
// conditions will be used when JOINing tables
// (such as find with LinkableBehavior)
'conditions' => array('OR' =>
array('%{MODEL_ALIAS}.debit_ledger_id = Ledger.id',
'%{MODEL_ALIAS}.credit_ledger_id = Ledger.id')),
// finderQuery will be used when tables are put
// together across several querys, not with JOIN.
// (such as find with ContainableBehavior)
'finderQuery' => 'NOT-IMPLEMENTED',
'counterQuery' => ''
),
);
var $hasAndBelongsToMany = array(
'DebitReconciliationLedgerEntry' => array(
'className' => 'LedgerEntry',
'joinTable' => 'reconciliations',
'foreignKey' => 'credit_ledger_entry_id',
'associationForeignKey' => 'debit_ledger_entry_id',
),
// STUPID CakePHP bug screws up when using Containable
// and CLASS contains CLASS. This extra HABTM give the
// option of multiple depths on one CLASS, since there
// isn't an alias specification for Containable.
'DebitReconciliationLedgerEntry2' => array(
'className' => 'LedgerEntry',
'joinTable' => 'reconciliations',
'foreignKey' => 'credit_ledger_entry_id',
'associationForeignKey' => 'debit_ledger_entry_id',
),
'CreditReconciliationLedgerEntry' => array(
'className' => 'LedgerEntry',
'joinTable' => 'reconciliations',
'foreignKey' => 'debit_ledger_entry_id',
'associationForeignKey' => 'credit_ledger_entry_id',
),
);
/**************************************************************************
**************************************************************************
**************************************************************************
* function: conditionEntryAsCreditOrDebit
* - returns the condition necessary to match a set of
* Ledgers to all related LedgerEntries
*/
function conditionEntryAsCreditOrDebit($ledger_ids) {
return array('OR' =>
array(array('debit_ledger_id' => $ledger_ids),
array('credit_ledger_id' => $ledger_ids)));
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: ledgerContext query helpers
* - Returns parameters necessary to generate a query which
* puts ledger entries into the context of a ledger. Since
* debit/credit depends on the account type, it is required
* as an argument for each function to avoid having to
* query the ledger/account to find it out.
*/
function ledgerContextFields($ledger_id = null, $account_type = null) {
$fields = array('id', 'effective_date', 'through_date',
'lease_id', 'customer_id', 'comment', 'amount');
if (isset($ledger_id)) {
$fields[] = ("IF(LedgerEntry.debit_ledger_id = $ledger_id," .
" LedgerEntry.amount, NULL) AS debit");
$fields[] = ("IF(LedgerEntry.credit_ledger_id = $ledger_id," .
" LedgerEntry.amount, NULL) AS credit");
if (isset($account_type)) {
if (in_array($account_type, array('ASSET', 'EXPENSE')))
$ledger_type = 'debit';
else
$ledger_type = 'credit';
$fields[] = ("(IF(LedgerEntry.{$ledger_type}_ledger_id = $ledger_id," .
" 1, -1) * LedgerEntry.amount) AS balance");
}
}
return $fields;
}
function ledgerContextFields2($ledger_id = null, $account_id = null, $account_type = null) {
$fields = array('id', 'effective_date', 'through_date', 'comment', 'amount');
if (isset($ledger_id)) {
$fields[] = ("IF(LedgerEntry.debit_ledger_id = $ledger_id," .
" SUM(LedgerEntry.amount), NULL) AS debit");
$fields[] = ("IF(LedgerEntry.credit_ledger_id = $ledger_id," .
" SUM(LedgerEntry.amount), NULL) AS credit");
if (isset($account_id) || isset($account_type)) {
$Account = new Account();
$account_ftype = $Account->fundamentalType($account_id ? $account_id : $account_type);
$fields[] = ("(IF(LedgerEntry.{$account_ftype}_ledger_id = $ledger_id," .
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
}
}
elseif (isset($account_id)) {
$fields[] = ("IF(DebitLedger.account_id = $account_id," .
" SUM(LedgerEntry.amount), NULL) AS debit");
$fields[] = ("IF(CreditLedger.account_id = $account_id," .
" SUM(LedgerEntry.amount), NULL) AS credit");
$Account = new Account();
$account_ftype = ucfirst($Account->fundamentalType($account_id));
$fields[] = ("(IF({$account_ftype}Ledger.account_id = $account_id," .
" 1, -1) * SUM(LedgerEntry.amount)) AS balance");
}
return $fields;
}
function ledgerContextConditions($ledger_id, $account_type) {
if (isset($ledger_id)) {
return array
('OR' =>
array(array('LedgerEntry.debit_ledger_id' => $ledger_id),
array('LedgerEntry.credit_ledger_id' => $ledger_id)),
);
}
return array();
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: findInLedgerContext
* - Returns an array of ledger entries that belong to a given ledger.
* There is extra logic to also figure out whether the ledger_entry
* amount is either a credit, or a debit, depending on how it was
* written into the ledger, as well as whether the amount increases or
* decreases the balance depending on the particular account type of
* the ledger.
*/
function findInLedgerContext($ledger_id, $account_type, $cond = null, $link = null) {
if (!isset($link))
$link = array('Transaction');
if (!isset($cond))
$cond = array();
$fields = $this->ledgerContextFields($ledger_id, $account_type);
$cond[] = $this->ledgerContextConditions($ledger_id, $account_type);
$order = array('Transaction.stamp');
$entries = $this->find
('all',
array('link' => $link,
'fields' => $fields,
'conditions' => $cond,
'order' => $order,
));
return $entries;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: findReconciledLedgerEntries
* - Returns ledger entries that are reconciled to the given entry.
* (such as payments towards a charge).
*/
function findReconciledLedgerEntries($id = null, $fundamental_type = null) {
foreach (($fundamental_type
? array($fundamental_type)
: array('debit', 'credit')) AS $fund) {
$ucfund = ucfirst($fund);
$reconciled[$fund]['entry'] = $this->find
('all', array
('link' => array
("ReconciliationLedgerEntry" => array
('class' => "{$ucfund}ReconciliationLedgerEntry",
'fields' => array
('id',
"COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'",
"LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
),
),
),
'group' => ("ReconciliationLedgerEntry.id"),
'conditions' => array('LedgerEntry.id' => $id),
'fields' => array(),
));
//pr($reconciled);
$balance = 0;
foreach ($reconciled[$fund]['entry'] AS &$entry) {
$entry = array_merge($entry["ReconciliationLedgerEntry"], $entry[0]);
$balance += $entry['balance'];
}
$reconciled[$fund]['balance'] = $balance;
}
return $reconciled;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reverse
* - Reverses the ledger entry
*/
function reverse1($id, $amount = null, $transaction_id = null, $rec_id = null) {
/* pr(array('LedgerEntry::reverse', */
/* compact('id', 'amount', 'transaction_id', 'rec_id'))); */
// Get the LedgerEntry and related fields
$entry = $this->find
('first',
array('contain' => array('MonetarySource.id',
'Transaction.id',
'DebitLedger.id',
'DebitLedger.account_id',
'CreditLedger.id',
'CreditLedger.account_id',
'DebitReconciliationLedgerEntry'
/* => */
/* array('DebitLedger.id', */
/* 'DebitLedger.account_id', */
/* 'CreditLedger.id', */
/* 'CreditLedger.account_id', */
/* ) */
,
'CreditReconciliationLedgerEntry'
/* => */
/* array('DebitLedger.id', */
/* 'DebitLedger.account_id', */
/* 'CreditLedger.id', */
/* 'CreditLedger.account_id', */
/* ) */
,
'Customer.id',
'Lease.id',
),
'fields' => array('LedgerEntry.*'),
'conditions' => array(array('LedgerEntry.id' => $id),
/* array('NOT' => */
/* array('OR' => */
/* array(array('DebitReconciliationLedgerEntry.id' => $rec_id), */
/* array('CreditReconciliationLedgerEntry.id' => $rec_id), */
/* ), */
/* ), */
/* ), */
),
));
//pr($entry);
if (!isset($amount))
$amount = $entry['LedgerEntry']['amount'];
$A = new Account();
$ids = $this->Ledger->Account->postLedgerEntry
(array('transaction_id' => $transaction_id),
null,
array('debit_ledger_id' => $A->currentLedgerID($entry['CreditLedger']['account_id']),
'credit_ledger_id' => $A->currentLedgerID($entry['DebitLedger']['account_id']),
'effective_date' => $entry['LedgerEntry']['effective_date'],
//'effective_date' => $entry['LedgerEntry']['effective_date'],
'amount' => $amount,
'lease_id' => $entry['Lease']['id'],
'customer_id' => $entry['Customer']['id'],
'comment' => "Reversal of Ledger Entry #{$id}",
),
array('debit' => array(array('LedgerEntry' => array('id' => $entry['LedgerEntry']['id'],
'amount' => $amount,
))),
'credit' => array(array('LedgerEntry' => array('id' => $entry['LedgerEntry']['id'],
'amount' => $amount,
))),
));
if ($ids['error'])
return null;
$tid = $ids['transaction_id'];
pr(compact('entry'));
foreach (array('Debit', 'Credit') AS $dc_type) {
foreach ($entry[$dc_type . 'ReconciliationLedgerEntry'] AS $RLE) {
pr(array('checkpoint' => "Reverse $dc_type LE",
compact('id', 'rec_id', 'RLE')));
if ($RLE['id'] == $rec_id) {
pr(array('checkpoint' => "Skipping Reverse $dc_type LE, due to rec_id",
compact('id', 'RLE')));
continue;
}
if (!$this->reverse($RLE['id'], $RLE['Reconciliation']['amount'], $tid, $id))
$ids['error'] = true;
/* $rids = $this->Ledger->Account->postLedgerEntry */
/* (array('transaction_id' => $tid), */
/* null, */
/* array('debit_ledger_id' => $A->currentLedgerID($RLE['CreditLedger']['account_id']), */
/* 'credit_ledger_id' => $A->currentLedgerID($RLE['DebitLedger']['account_id']), */
/* 'effective_date' => $RLE['effective_date'], */
/* //'effective_date' => $RLE['effective_date'], */
/* 'amount' => $RLE['Reconciliation']['amount'], */
/* 'lease_id' => $entry['Lease']['id'], */
/* 'customer_id' => $entry['Customer']['id'], */
/* 'comment' => "Reversal of Ledger Entry #{$RLE['id']}", */
/* ), */
/* array('debit' => array(array('LedgerEntry' => array('id' => $RLE['id'], */
/* 'amount' => $RLE['Reconciliation']['amount'], */
/* ))), */
/* 'credit' => array(array('LedgerEntry' => array('id' => $RLE['id'], */
/* 'amount' => $RLE['Reconciliation']['amount'], */
/* ))), */
/* )); */
/* if ($rids['error']) */
/* $ids['error'] = true; */
}
}
if ($ids['error'])
return null;
return $ids['id'];
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reverse
* - Reverses the charges
*
* SAMPLE MOVE IN w/ PRE PAYMENT
* DEPOSIT RENT A/R RECEIPT CHECK PETTY BANK
* ------- ------- ------- ------- ------- ------- -------
* |25 | 25| | | | |
* | |20 20| | | | |
* | |20 20| | | | |
* | |20 20| | | | |
* | | |25 25| | | |
* | | |20 20| | | |
* | | |20 20| | | |
* | | |20 20| | | |
* | | | |85 85| | |
* | | | | |85 | 85|
* MOVE OUT and REFUND FINAL MONTH
* DEPOSIT RENT C/P RECEIPT CHECK PETTY BANK
* ------- ------- ------- ------- ------- ------- -------
* 25| | |25 | | | | t20 e20a
* | 20| |20 | | | | t20 e20b
* -ONE REFUND CHECK-
* | | 25| |25 | | | t30 e30a
* | | 20| |20 | | | t30 e30b
* | | | 45| | | |45 t40 e40
* -OR MULTIPLE-
* | | 15| |15 | | | t50a e50a
* | | | 15| | |15 | t60a e60a
* | | 30| |30 | | | t50b e50b
* | | | 30| | | |30 t60b e60b
* | | | | | | |
OPTION 1
* |-25 | -25| | | | |
* | |-20 -20| | | | |
* | | |-25 -25| | | |
* | | |-20 -20| | | |
OPTION 2
* |-25 | | -25| | | |
* | |-20 | -20| | | |
* | | | |-15 | -15| |
* | | | |-30 | | -30|
* | | | | | | |
*
*/
function reverse($ledger_entries, $stamp = null) {
pr(array('LedgerEntry::reverse',
compact('ledger_entries', 'stamp')));
// If the user only wants to reverse one ID, we'll allow it
if (!is_array($ledger_entries))
$ledger_entries = $this->find
('all', array
('contain' => false,
'conditions' => array('LedgerEntry.id' => $ledger_entries)));
$A = new Account();
$ar_account_id = $A->accountReceivableAccountID();
$receipt_account_id = $A->receiptAccountID();
$transaction_id = null;
foreach ($ledger_entries AS $entry) {
$entry = $entry['LedgerEntry'];
$amount = -1*$entry['amount'];
if (isset($entry['credit_account_id']))
$refund_account_id = $entry['credit_account_id'];
elseif (isset($entry['CreditLedger']['Account']['id']))
$refund_account_id = $entry['CreditLedger']['Account']['id'];
elseif (isset($entry['credit_ledger_id']))
$refund_account_id = $this->Ledger->accountID($entry['credit_ledger_id']);
else
return null;
// post new refund in the income account
$ids = $A->postLedgerEntry
(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('LedgerEntry' =>
array('id' => $entry['id'],
'amount' => $amount))),
)
);
if ($ids['error'])
return null;
$transaction_id = $ids['transaction_id'];
pr(array('checkpoint' => 'Posted Refund Ledger Entry',
compact('ids', 'amount', 'refund_account_id', 'ar_account_id')));
}
return true;
}
/**************************************************************************
**************************************************************************
**************************************************************************
* function: stats
* - Returns summary data from the requested ledger entry
*/
function stats($id) {
$query = array
(
'fields' => array("SUM(Reconciliation.amount) AS 'reconciled'"),
'conditions' => array(isset($cond) ? $cond : array(),
array('LedgerEntry.id' => $id)),
'group' => 'LedgerEntry.id',
);
// Get the applied amounts on the debit side
$query['link'] =
array('DebitReconciliationLedgerEntry' => array('alias' => 'DRLE', 'DRLETransaction' => array('class' => 'Transaction')));
$tmpstats = $this->find('first', $query);
$stats['debit_amount_reconciled'] = $tmpstats[0]['reconciled'];
// Get the applied amounts on the credit side
$query['link'] =
array('CreditReconciliationLedgerEntry' => array('alias' => 'CRLE', 'CRLETransaction' => array('class' => 'Transaction')));
$tmpstats = $this->find('first', $query);
$stats['credit_amount_reconciled'] = $tmpstats[0]['reconciled'];
return $stats;
}
}

View File

@@ -1,20 +1,59 @@
<?php <?php
class MonetarySource extends AppModel { class Payment extends AppModel {
var $name = 'MonetarySource';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
'tillable' => array('boolean')
);
var $belongsTo = array( var $belongsTo = array(
'Customer',
'Lease',
); );
var $hasMany = array( var $hasMany = array(
'LedgerEntry', 'DoubleEntry',
); );
var $hasAndBelongsToMany = array(
'DoubleEntry',
);
/**************************************************************************
**************************************************************************
**************************************************************************
* function: addPayment
* - Adds a new payment
*/
function addPayment($data, $customer_id, $lease_id = null, $reconcile = null) {
// Create some models for convenience
$A = new Account();
// Assume this will succeed
$ret = true;
// Establish the key payment parameters
$payment = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1,
'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1));
$payment['customer_id'] = $customer_id;
// Create the payment entry, and reconcile the credit side
// of the double-entry (which should be A/R) as a payment.
$ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
($payment,
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
'credit_ledger_id' => $A->currentLedgerID($A->paymentAccountID())
) + $entry,
array('debit' => 'payment',
'credit' => $reconcile)
);
if ($ids['error'])
$ret = false;
$payment = array_intersect_key($ids,
array('payment_id'=>1,
'split_payment_id'=>1));
return $ret;
}
/************************************************************************** /**************************************************************************
************************************************************************** **************************************************************************
@@ -48,7 +87,7 @@ class MonetarySource extends AppModel {
*/ */
function nsf($id, $stamp = null) { function nsf($id, $stamp = null) {
pr(array('MonetarySource::nsf', pr(array('Payment::nsf',
compact('id'))); compact('id')));
$A = new Account(); $A = new Account();
@@ -58,9 +97,9 @@ class MonetarySource extends AppModel {
('first', ('first',
array('contain' => array('contain' =>
array(/* e3 */ array(/* e3 */
'LedgerEntry' => 'DoubleEntry' =>
array('Transaction.id', array('Transaction.id',
'MonetarySource.id', 'Payment.id',
'Customer.id', 'Customer.id',
'Lease.id', 'Lease.id',
@@ -87,7 +126,7 @@ class MonetarySource extends AppModel {
), ),
/* e2 */ /* e2 */
'DebitReconciliationLedgerEntry' => 'DebitReconciliationDoubleEntry' =>
array(/* e2 credit */ array(/* e2 credit */
'CreditLedger' => /* i.e. A/R Ledger */ 'CreditLedger' => /* i.e. A/R Ledger */
array('fields' => array(), array('fields' => array(),
@@ -102,11 +141,11 @@ class MonetarySource extends AppModel {
/* e1 */ /* e1 */
// STUPID CakePHP bug screws up CLASS contains CLASS. // STUPID CakePHP bug screws up CLASS contains CLASS.
// Use the same class, but with different name. // Use the same class, but with different name.
'DebitReconciliationLedgerEntry2', 'DebitReconciliationDoubleEntry2',
), ),
/* e4 */ /* e4 */
'CreditReconciliationLedgerEntry' => 'CreditReconciliationDoubleEntry' =>
array(/* e4 debit */ array(/* e4 debit */
'DebitLedger' => /* e.g. BANK Ledger */ 'DebitLedger' => /* e.g. BANK Ledger */
array('fields' => array('id'), array('fields' => array('id'),
@@ -120,7 +159,7 @@ class MonetarySource extends AppModel {
), ),
), ),
'conditions' => array(array('MonetarySource.id' => $id)), 'conditions' => array(array('Payment.id' => $id)),
)); ));
pr($source); pr($source);
@@ -131,9 +170,9 @@ class MonetarySource extends AppModel {
$t4_id = null; $t4_id = null;
$t5_id = null; $t5_id = null;
foreach ($source['LedgerEntry'] AS $e3) { foreach ($source['DoubleEntry'] AS $e3) {
// We expect only a single e4 entry // We expect only a single e4 entry
$e4 = $e3['CreditReconciliationLedgerEntry']; $e4 = $e3['CreditReconciliationDoubleEntry'];
if (count($e4) < 1) if (count($e4) < 1)
continue; continue;
if (count($e4) > 1) if (count($e4) > 1)
@@ -149,7 +188,7 @@ class MonetarySource extends AppModel {
$bank_account_id = $e4['DebitLedger']['account_id']; $bank_account_id = $e4['DebitLedger']['account_id'];
// post new e5 // post new e5
$e5_ids = $A->postLedgerEntry $e5_ids = $A->postDoubleEntry
(array('transaction_id' => $t4_id), (array('transaction_id' => $t4_id),
null, null,
array('debit_ledger_id' => $A->currentLedgerID($bank_account_id), array('debit_ledger_id' => $A->currentLedgerID($bank_account_id),
@@ -172,7 +211,7 @@ class MonetarySource extends AppModel {
// post new e6... this will be our crossover point // post new e6... this will be our crossover point
// from typical positive entries to negative entries. // from typical positive entries to negative entries.
// Therefore, no reconciliation on this account. // Therefore, no reconciliation on this account.
$e6_ids = $A->postLedgerEntry $e6_ids = $A->postDoubleEntry
(array('transaction_id' => $t5_id), (array('transaction_id' => $t5_id),
array('monetary_source_id' => $e3['monetary_source_id']), array('monetary_source_id' => $e3['monetary_source_id']),
array('debit_ledger_id' => $A->currentLedgerID($nsf_account_id), array('debit_ledger_id' => $A->currentLedgerID($nsf_account_id),
@@ -184,7 +223,7 @@ class MonetarySource extends AppModel {
'comment' => "NSF tracker; Monetary Source #{$id}", 'comment' => "NSF tracker; Monetary Source #{$id}",
), ),
array('debit' => array array('debit' => array
(array('LedgerEntry' => (array('DoubleEntry' =>
array('id' => $e5_ids['id'], array('id' => $e5_ids['id'],
'amount' => $amount))), 'amount' => $amount))),
) )
@@ -198,12 +237,12 @@ class MonetarySource extends AppModel {
compact('e6_ids', 'amount'))); compact('e6_ids', 'amount')));
$t6_id = null; $t6_id = null;
foreach ($e3['DebitReconciliationLedgerEntry'] AS $e2) { foreach ($e3['DebitReconciliationDoubleEntry'] AS $e2) {
foreach ($e2['DebitReconciliationLedgerEntry2'] AS $e1) { foreach ($e2['DebitReconciliationDoubleEntry2'] AS $e1) {
$amount = -1*$e1['Reconciliation']['amount']; $amount = -1*$e1['Reconciliation']['amount'];
// post new e7 // post new e7
$e7_ids = $A->postLedgerEntry $e7_ids = $A->postDoubleEntry
(array('transaction_id' => $t6_id), (array('transaction_id' => $t6_id),
null, null,
array('debit_ledger_id' => $A->currentLedgerID($receipt_account_id), array('debit_ledger_id' => $A->currentLedgerID($receipt_account_id),
@@ -215,12 +254,12 @@ class MonetarySource extends AppModel {
'comment' => "NSF Receipt; Monetary Source #{$id}", 'comment' => "NSF Receipt; Monetary Source #{$id}",
), ),
array('debit' => array array('debit' => array
(array('LedgerEntry' => (array('DoubleEntry' =>
array('id' => $e6_ids['id'], array('id' => $e6_ids['id'],
'amount' => $amount))), 'amount' => $amount))),
'credit' => array 'credit' => array
(array('LedgerEntry' => (array('DoubleEntry' =>
array('id' => $e1['id'], array('id' => $e1['id'],
'amount' => $amount))), 'amount' => $amount))),
) )
@@ -237,11 +276,11 @@ class MonetarySource extends AppModel {
} }
// Cheat for now // Cheat for now
$customer_id = $source['LedgerEntry'][0]['customer_id']; $customer_id = $source['DoubleEntry'][0]['customer_id'];
$lease_id = null; $lease_id = null;
// post new e8 // post new e8
$e8_ids = $A->postLedgerEntry $e8_ids = $A->postDoubleEntry
(array('transaction_id' => $t6_id), (array('transaction_id' => $t6_id),
null, null,
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id), array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),

View File

@@ -2,13 +2,11 @@
class Reconciliation extends AppModel { class Reconciliation extends AppModel {
var $belongsTo = array( var $belongsTo = array(
'DebitLedgerEntry' => array( 'DebitEntry' => array(
'className' => 'LedgerEntry', 'className' => 'Entry',
//'foreignKey' => 'credit_ledger_entry_id',
), ),
'CreditLedgerEntry' => array( 'CreditEntry' => array(
'className' => 'LedgerEntry', 'className' => 'Entry',
//'foreignKey' => 'credit_ledger_entry_id',
), ),
); );

View File

@@ -1,17 +1,17 @@
<?php <?php
class Transaction extends AppModel { class Transaction extends AppModel {
var $name = 'Transaction';
var $validate = array( var $validate = array(
'stamp' => array('date') 'stamp' => array('date')
); );
var $belongsTo = array( var $belongsTo = array(
'Customer',
'Lease',
); );
var $hasMany = array( var $hasMany = array(
'LedgerEntry', 'DoubleEntry',
); );
@@ -19,7 +19,7 @@ class Transaction extends AppModel {
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: addInvoice * function: addInvoice
* - Adds a new invoice transaction * - Adds a new invoice invoice
*/ */
function addInvoice($data, $customer_id, $lease_id = null) { function addInvoice($data, $customer_id, $lease_id = null) {
@@ -31,32 +31,31 @@ class Transaction extends AppModel {
// Assume this will succeed // Assume this will succeed
$ret = true; $ret = true;
// Establish the key invoice parameters
$invoice = array_intersect_key($data, array('Invoice'=>1));
$invoice['type'] = 'INVOICE';
// Determine the total charges on the invoice // Determine the total charges on the invoice
$grand_total = 0; $invoice['amount'] = 0;
foreach ($data['LedgerEntry'] AS $entry) foreach ($data['DoubleEntry'] AS $entry)
$grand_total += $entry['amount']; $invoice['amount'] += $entry['amount'];
// Go through the entered charges // Go through the entered charges
$invoice_transaction = array_intersect_key($data, array('Transaction'=>1, 'transaction_id'=>1)); foreach ($data['DoubleEntry'] AS $entry) {
foreach ($data['LedgerEntry'] AS $entry) {
//pr(compact('entry')); //pr(compact('entry'));
// Create the receipt entry, and reconcile the credit side // Create the receipt entry, and reconcile the credit side
// of the double-entry (which should be A/R) as a payment. // of the double-entry (which should be A/R) as a payment.
$ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry $ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
($invoice_transaction, ($invoice,
array_intersect_key($entry, array('MonetarySource'=>1))
+ array_intersect_key($entry, array('account_id'=>1)),
array('debit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()), array('debit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()),
'credit_ledger_id' => $A->currentLedgerID($entry['account_id']), 'credit_ledger_id' => $A->currentLedgerID($entry['account_id'])
'customer_id' => $customer_id, ) + $entry
'lease_id' => $lease_id)
+ $entry
); );
if ($ids['error']) if ($ids['error'])
$ret = false; $ret = false;
$invoice_transaction = array_intersect_key($ids, array('transaction_id'=>1)); $invoice = array_intersect_key($ids, array('invoice_id'=>1));
} }
return $ret; return $ret;
@@ -67,7 +66,7 @@ class Transaction extends AppModel {
************************************************************************** **************************************************************************
************************************************************************** **************************************************************************
* function: addReceipt * function: addReceipt
* - Adds a new receipt transaction * - Adds a new receipt
*/ */
function addReceipt($data, $customer_id, $lease_id = null) { function addReceipt($data, $customer_id, $lease_id = null) {
@@ -77,32 +76,40 @@ class Transaction extends AppModel {
// Assume this will succeed // Assume this will succeed
$ret = true; $ret = true;
// Go through the entered payments // Establish the key receipt parameters
$receipt_transaction = array_intersect_key($data, array('Transaction'=>1, 'transaction_id'=>1)); $receipt = array_intersect_key($data, array('stamp'=>1, 'type'=>1, 'name'=>1, 'amount'=>1,
foreach ($data['LedgerEntry'] AS $entry) { 'data1'=>1, 'data2'=>1, 'data3'=>1, 'data4'=>1));
$receipt['type'] = 'RECEIPT';
// Determine the total charges on the receipt
$receipt['amount'] = 0;
foreach ($data['DoubleEntry'] AS $entry)
$receipt['amount'] += $entry['amount'];
// Go through the entered charges
foreach ($data['DoubleEntry'] AS $entry) {
// Create the receipt entry, and reconcile the credit side // Create the receipt entry, and reconcile the credit side
// of the double-entry (which should be A/R) as a payment. // of the double-entry (which should be A/R) as a receipt.
$ids = $this->LedgerEntry->Ledger->Account->postLedgerEntry $ids = $this->DoubleEntry->Ledger->Account->postDoubleEntry
($receipt_transaction, ($receipt,
array_intersect_key($entry, array('MonetarySource'=>1))
+ array_intersect_key($entry, array('account_id'=>1)),
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']), array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()), 'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID())
'customer_id' => $customer_id, ) + $entry,
'lease_id' => $lease_id) array('debit' => 'receipt',
+ $entry, 'credit' => $reconcile)
'receipt'); );
if ($ids['error']) if ($ids['error'])
$ret = false; $ret = false;
$receipt_transaction = array_intersect_key($ids, $receipt = array_intersect_key($ids,
array('transaction_id'=>1, array('receipt_id'=>1,
'split_transaction_id'=>1)); 'split_receipt_id'=>1));
} }
return $ret; return $ret;
} }
} }
?> ?>

View File

@@ -50,10 +50,11 @@ echo '<div class="ledger-entry view">' . "\n";
$transaction = $entry['Transaction']; $transaction = $entry['Transaction'];
$ledgers = array('debit' => $entry['DebitLedger'], $ledgers = array('debit' => $entry['DebitLedger'],
'credit' => $entry['CreditLedger']); 'credit' => $entry['CreditLedger']);
$source = $entry['MonetarySource']; $entries = array('debit' => $entry['DebitEntry'],
'credit' => $entry['CreditEntry']);
$customer = $entry['Customer']; $customer = $entry['Customer'];
$lease = $entry['Lease']; $lease = $entry['Lease'];
$entry = $entry['LedgerEntry']; $entry = $entry['DoubleEntry'];
$rows = array(); $rows = array();
$rows[] = array('ID', $entry['id']); $rows[] = array('ID', $entry['id']);
@@ -63,7 +64,7 @@ $rows[] = array('Transaction', $html->link('#'.$transaction['id'],
$transaction['id']))); $transaction['id'])));
$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp'])); $rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp']));
$rows[] = array('Effective', FormatHelper::date($entry['effective_date'])); $rows[] = array('Effective', FormatHelper::date($entry['effective_date']));
$rows[] = array('Through', FormatHelper::date($entry['through_date'])); //$rows[] = array('Through', FormatHelper::date($entry['through_date']));
$rows[] = array('Customer', (isset($customer['name']) $rows[] = array('Customer', (isset($customer['name'])
? $html->link($customer['name'], ? $html->link($customer['name'],
array('controller' => 'customers', array('controller' => 'customers',
@@ -76,12 +77,6 @@ $rows[] = array('Lease', (isset($lease['id'])
'action' => 'view', 'action' => 'view',
$lease['id'])) $lease['id']))
: null)); : null));
$rows[] = array('Monetary Source', (isset($source['name'])
? $html->link($source['name'],
array('controller' => 'monetary_sources',
'action' => 'view',
$source['id']))
: null));
$rows[] = array('Comment', $entry['comment']); $rows[] = array('Comment', $entry['comment']);
echo $this->element('table', echo $this->element('table',
@@ -95,35 +90,39 @@ echo $this->element('table',
* LedgerEntry Info Box * LedgerEntry Info Box
*/ */
echo '<div class="infobox">' . "\n"; /* echo '<div class="infobox">' . "\n"; */
foreach ($ledgers AS $type => $ledger) { /* foreach ($ledgers AS $type => $ledger) { */
//pr($ledger); /* //pr($ledger); */
if (!$ledger['Account']['trackable']) /* if (!$ledger['Account']['trackable']) */
continue; /* continue; */
$applied_caption = "Transfers applied"; /* $applied_caption = "Transfers applied"; */
$remaining_caption = "Unapplied amount"; /* $remaining_caption = "Unapplied amount"; */
$rows = array(); /* $rows = array(); */
$rows[] = array($applied_caption, /* $rows[] = array($applied_caption, */
FormatHelper::currency($stats[$type]['amount_reconciled'])); /* FormatHelper::currency($stats[$type]['amount_reconciled'])); */
$rows[] = array($remaining_caption, /* $rows[] = array($remaining_caption, */
FormatHelper::currency($stats[$type]['amount_remaining'])); /* FormatHelper::currency($stats[$type]['amount_remaining'])); */
echo $this->element('table', /* echo $this->element('table', */
array('class' => 'item summary', /* array('class' => 'item summary', */
'caption' => "{$ledger['Account']['name']} Ledger Entry", /* 'caption' => "{$ledger['Account']['name']} Ledger Entry", */
'rows' => $rows, /* 'rows' => $rows, */
'column_class' => array('field', 'value'), /* 'column_class' => array('field', 'value'), */
//'suppress_alternate_rows' => true, /* //'suppress_alternate_rows' => true, */
)); /* )); */
} /* } */
echo '</div>' . "\n"; /* echo '</div>' . "\n"; */
echo ('<DIV CLASS="ledger-double-entry">' . "\n"); echo ('<DIV CLASS="ledger-double-entry">' . "\n");
foreach ($ledgers AS $type => $ledger) { foreach ($ledgers AS $type => $ledger) {
$rows = array(); $rows = array();
$rows[] = array('ID', $html->link('#' . $entries[$type]['id'],
array('controller' => 'entries',
'action' => 'view',
$entries[$type]['id'])));
$rows[] = array('Account', $html->link($ledger['Account']['name'], $rows[] = array('Account', $html->link($ledger['Account']['name'],
array('controller' => 'accounts', array('controller' => 'accounts',
'action' => 'view', 'action' => 'view',
@@ -156,30 +155,6 @@ echo ('</DIV>' . "\n");
echo '<div CLASS="detail supporting">' . "\n"; echo '<div CLASS="detail supporting">' . "\n";
/**********************************************************************
* Reconciliation Ledger Entries
*/
foreach ($ledgers AS $type => $ledger) {
if (!$ledger['Account']['trackable'])
continue;
$caption = ('Applied transfers ' . ($ledger['Account']['ftype'] == $type ? 'out of' : 'into') .
' Account: ' . $ledger['Account']['name']);
echo $this->element('ledger_entries', array
(// Element configuration
'account_ftype' => $type,
'reconcile_id' => $entry['id'],
// Grid configuration
'config' => array
('caption' => $caption,
'grid_div_id' => $type.'_reconciliation_ledger_entries',
),
));
}
/* End "detail supporting" div */ /* End "detail supporting" div */
echo '</div>' . "\n"; echo '</div>' . "\n";

View File

@@ -109,15 +109,20 @@ if (!isset($config['rows']) && !isset($collected_account_id)) {
} }
if (isset($reconcile_id)) { if (isset($reconcile_id)) {
$config['action'] = 'reconcile';
$grid->customData(compact('reconcile_id'))->limit(20); $grid->customData(compact('reconcile_id'))->limit(20);
} }
if (isset($collected_account_id)) { if (isset($collected_account_id)) {
$config['action'] = 'collected'; $config['action'] = 'collected';
$grid->customData(compact('collected_account_id'))->limit(50); $account_id = $collected_account_id;
$grid->limit(50);
$grid->sortField('Last Payment'); $grid->sortField('Last Payment');
} }
if (isset($entry_ids))
$grid->id_list($entry_ids);
// Set up search fields if requested by caller // Set up search fields if requested by caller
if (isset($searchfields)) if (isset($searchfields))
$grid->searchFields(array('Customer', 'Unit')); $grid->searchFields(array('Customer', 'Unit'));

View File

@@ -0,0 +1,143 @@
<?php /* -*- mode:PHP -*- */
// Define the table columns
$cols = array();
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
$cols['Entry'] = array('index' => 'Entry.id', 'formatter' => 'id');
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
$cols['Effective'] = array('index' => 'DoubleEntry.effective_date', 'formatter' => 'date');
$cols['Through'] = array('index' => 'Entry.through_date', 'formatter' => 'date');
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name');
$cols['Cr/Dr'] = array('index' => 'Entry.crdr', 'formatter' => 'name');
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id');
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'name');
$cols['Source'] = array('index' => 'Entry.name', 'formatter' => 'name');
$cols['Comment'] = array('index' => 'Entry.comment', 'formatter' => 'comment', 'width'=>150);
$cols['Amount'] = array('index' => 'DoubleEntry.amount', 'formatter' => 'currency');
$cols['Last Payment'] = array('index' => 'last_paid', 'formatter' => 'date');
$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency');
$cols['Sub-Total'] = array('index' => 'subtotal-DoubleEntry.amount', 'formatter' => 'currency', 'sortable' => false);
// Since group_by_tx is a boolean, let's just get it
// defined, regardless of whether the caller did so.
// group_by_tx will cause all entry fields to be
// invalidated, and will leave only the transaction
// fields. Yes... the caller should just use the
// transactions element instead, in theory. However,
// it hasn't yet been implemented to the level of
// this element, and additionally, the transactions
// element will not allow for customer information
// (rightly so, since it's a ledger_entry field).
// However, at the current implementation, all ledger
// entries of a transaction are for the same customer.
// So... we allow it for now.
if (!isset($group_by_tx))
$group_by_tx = false;
// REVISIT <AP>: 20090715
// If we really want to group by transaction, we need
// a transaction listing, not a ledger_entry listing.
// switch controllers... don't overload this one.
$group_by_tx = false;
if (isset($transaction_id) || isset($reconcile_id))
$grid->invalidFields('Transaction');
if ($group_by_tx)
$grid->invalidFields('Entry');
if ($group_by_tx)
$grid->invalidFields(array('Effective', 'Through'));
if (!isset($collected_account_id))
$grid->invalidFields('Last Payment');
if (isset($account_ftype) || isset($ledger_id) || isset($account_id) || isset($ar_account))
$grid->invalidFields(array('Debit Account', 'Credit Account'));
else
$grid->invalidFields('Account');
if (isset($no_account) || $group_by_tx || isset($collected_account_id))
$grid->invalidFields(array('Account', 'Debit Account', 'Credit Account'));
if (isset($ledger_id) || isset($account_id) || isset($ar_account)) {
$grid->invalidFields('Amount');
$cols['Sub-Total']['index'] = 'subtotal-balance';
} else {
$grid->invalidFields(array('Debit', 'Credit'));
$cols['Sub-Total']['index'] = 'subtotal-DoubleEntry.amount';
}
// group_by_tx SHOULD wipe out Customer, but the reality
// is that it works good at the present, so we'll leave it.
if (isset($lease_id) || isset($customer_id))
$grid->invalidFields(array('Customer'));
if (isset($lease_id) || $group_by_tx)
$grid->invalidFields(array('Lease', 'Unit'));
if (!isset($reconcile_id) && !isset($collected_account_id))
$grid->invalidFields('Applied');
else
$cols['Sub-Total']['index'] = 'subtotal-applied';
if (isset($account_ftype) || isset($collected_account_id))
$grid->invalidFields('Sub-Total');
// Now that columns are defined, establish basic grid parameters
$grid
->columns($cols)
->sortField('Date')
->defaultFields(array('Entry', 'Date', 'Amount', 'Credit', 'Debit'));
if (!isset($config['rows']) && !isset($collected_account_id)) {
$config['action'] = 'ledger';
$grid->limit(50);
}
if (isset($reconcile_id)) {
$config['action'] = 'reconcile';
$grid->customData(compact('reconcile_id'))->limit(20);
}
if (isset($collected_account_id)) {
$config['action'] = 'collected';
$account_id = $collected_account_id;
$grid->limit(50);
$grid->sortField('Last Payment');
}
if (isset($entry_ids)) {
unset($config['action']);
$grid->id_list($entry_ids);
}
// Set up search fields if requested by caller
if (isset($searchfields))
$grid->searchFields(array('Customer', 'Unit'));
// Include custom data
$grid->customData(compact('ledger_id', 'account_id', 'ar_account',
'account_type', 'account_ftype', 'monetary_source_id',
'customer_id', 'lease_id', 'transaction_id', 'group_by_tx'));
// Render the grid
$grid
->render($this, isset($config) ? $config : null,
array('Transaction', 'Entry', 'Date', 'Effective', 'Last Payment',
'Account', 'Cr/Dr',
'Customer', 'Unit',
'Comment',
'Amount',
'Applied', 'Sub-Total')
);

165
site/views/entries/view.ctp Normal file
View File

@@ -0,0 +1,165 @@
<?php /* -*- mode:PHP -*- */
echo '<div class="entry view">' . "\n";
// The two entry ids, debit and credit, are actually individual
// entries in separate accounts (each make up one of the two
// entries required for "double entry"). The reconciling entries
// are those on the opposite side of the ledger in the specific
// account. For example, assume the double entry is:
// debit: A/R credit: Cash amount: 55
//
// Then, our accounts might look like:
//
// RENT TAX A/R CASH BANK
// ------- ------- ------- ------- -------
// |20 | 20| | | <-- Unrelated
// | | |20 20| | <-- Unrelated
// | | | | |
// |50 | 50| | | <-- Rent paid by this entry
// | |5 5| | | <-- Tax paid by this entry
// | | |55 55| | <-- THIS DOUBLE ENTRY
// | | | | |
// | | | |75 75| <-- Deposit includes this entry
// | | | | |
//
// In this case, assume that THIS specific Entry is the A/R credit
// of the Double Entry. We'll need to provide information on the
// two A/R entries, 50 & 5, which are both debits, i.e. opposite
// entries to the credit of A/R.
/**********************************************************************
**********************************************************************
**********************************************************************
**********************************************************************
* Entry Detail Main Section
*/
$account = $entry['Account'];
$double = $entry['DoubleEntry'];
$transaction = $double['Transaction'];
$customer = $double['Customer'];
$lease = $double['Lease'];
$entry = $entry['Entry'];
$rows = array();
$rows[] = array('ID', $entry['id']);
$rows[] = array('Transaction', $html->link('#'.$transaction['id'],
array('controller' => 'transactions',
'action' => 'view',
$transaction['id'])));
$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp']));
$rows[] = array('Effective', FormatHelper::date($double['effective_date']));
$rows[] = array('Through', FormatHelper::date($entry['through_date']));
$rows[] = array('Account', $html->link($account['name'],
array('controller' => 'accounts',
'action' => 'view',
$account['id'])));
$rows[] = array('Cr/Dr', ($entry['crdr'] .
' (Matching ' . $entry['opposite_crdr'] . ': ' .
$html->link('#'.$entry['matching_entry_id'],
array('controller' => 'entries',
'action' => 'view',
$entry['matching_entry_id'])) .
')'));
$rows[] = array('Double Entry', $html->link('#'.$double['id'],
array('controller' => 'double_entries',
'action' => 'view',
$double['id'])));
$rows[] = array('Customer', (isset($customer['name'])
? $html->link($customer['name'],
array('controller' => 'customers',
'action' => 'view',
$customer['id']))
: null));
$rows[] = array('Lease', (isset($lease['id'])
? $html->link('#'.$lease['id'],
array('controller' => 'leases',
'action' => 'view',
$lease['id']))
: null));
$rows[] = array('Comment', $entry['comment']);
echo $this->element('table',
array('class' => 'item entry detail',
'caption' => 'Ledger Entry Detail',
'rows' => $rows,
'column_class' => array('field', 'value')));
/**********************************************************************
* Entry Info Box
*/
echo '<div class="infobox">' . "\n";
$applied_caption = "Transfers applied";
$remaining_caption = "Unapplied amount";
foreach ($reconciled['stats'] AS $Rtype => $stats) {
$rtype = strtolower($Rtype);
$applied_caption = "Transfers applied";
$remaining_caption = "Unapplied amount";
/* $applied_caption = $Rtype . 's Applied'; */
/* $remaining_caption = 'Remaining for ' . $Rtype . 's'; */
$rows = array();
$rows[] = array($applied_caption,
'<SPAN id="'.$rtype.'-applied">' .
FormatHelper::currency($stats['applied']) .
'</SPAN>');
$rows[] = array($remaining_caption,
'<SPAN id="'.$rtype.'-unapplied">' .
FormatHelper::currency($stats['unapplied']) .
'</SPAN>');
echo $this->element('table',
array('class' => 'item summary',
'caption' => $Rtype . 's',
'rows' => $rows,
'column_class' => array('field', 'value'),
//'suppress_alternate_rows' => true,
));
}
echo '</div>' . "\n";
/**********************************************************************
**********************************************************************
**********************************************************************
**********************************************************************
* Supporting Elements Section
*/
echo '<div CLASS="detail supporting">' . "\n";
/**********************************************************************
* Reconciliation Ledger Entries
*/
foreach ($reconciled['entries'] AS $Rtype => $entries) {
$rtype = strtolower($Rtype);
$caption = $Rtype . 's applied';
echo $this->element('entries', array
(// Element configuration
'entry_ids' => $entries,
// Grid configuration
'config' => array
('caption' => $caption,
'grid_div_id' => $rtype.'-entries',
),
));
}
/* End "detail supporting" div */
echo '</div>' . "\n";
/* End page div */
echo '</div>' . "\n";

View File

@@ -91,6 +91,7 @@ class GridHelper extends AppHelper {
= array_map(create_function('$data', = array_map(create_function('$data',
'return $data["id"];'), 'return $data["id"];'),
$items); $items);
$this->jqGrid_options['action'] = 'idlist';
return $this; return $this;
} }

View File

@@ -19,9 +19,11 @@ echo ('<DIV CLASS="apply-deposit grid-selection-text">' .
'</DIV>' . "\n"); '</DIV>' . "\n");
echo $form->create(null, array('id' => 'receipt-form', echo $form->create(null, array('id' => 'apply-deposit-form',
'url' => array('controller' => 'transactions', /* 'url' => array('controller' => 'lease', */
'action' => 'postReceipt'))); /* 'action' => 'apply_deposit') */
)
);
echo $form->input("Customer.id", echo $form->input("Customer.id",
array('id' => 'customer-id', array('id' => 'customer-id',
@@ -33,7 +35,7 @@ echo $form->input("Lease.id",
'type' => 'hidden', 'type' => 'hidden',
'value' => $lease['id'])); 'value' => $lease['id']));
echo $form->input("LedgerEntry.0.account_id", echo $form->input("LedgerEntry.Account.id",
array('id' => 'account-id', array('id' => 'account-id',
'type' => 'hidden', 'type' => 'hidden',
'value' => $account['id'])); 'value' => $account['id']));
@@ -47,7 +49,10 @@ echo $this->element('form_table',
("stamp" => array('opts' => array('type' => 'text'), ("stamp" => array('opts' => array('type' => 'text'),
'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>', 'between' => '<A HREF="#" ONCLICK="datepickerNow(\'TransactionStamp\'); return false;">Now</A>',
), ),
"amount" => array('prefix' => 'LedgerEntry.0'), "amount" => array('prefix' => 'LedgerEntry',
'value' => min($lease['stats']['balance'],
$deposit['summary']['balance']),
),
"comment" => array('opts' => array('size' => 50), "comment" => array('opts' => array('size' => 50),
), ),
))); )));

View File

@@ -0,0 +1,74 @@
<?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 '<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),
),
));
}
$options = array();
foreach ($depositableAccount AS $acct) {
$options[$acct['Account']['id']] = $acct['Account']['name'];
}
echo $form->input('Deposit.Account.id', array('label' => 'Deposit Account ',
'options' => $options));
echo $form->end('Perform Deposit');
/* End page div */
echo '</div>' . "\n";