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`)
VALUES
('ASSET', 'A/R' ),
('ASSET', 'Invoice' ),
('ASSET', 'Receipt' ),
-- REVISIT <AP>: 20090710 : We don't really need NSF, as it
-- will always run a zero balance. However, it will help
-- 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', 'NSF Charge', 1, 0),
('INCOME', 'Damage', 1, 0);
INSERT INTO `pmgr_accounts` (`type`, `name`, `depositable`, `trackable`)
INSERT INTO `pmgr_accounts` (`type`, `name`, `depositable`, `refundable`, `trackable`)
VALUES
('ASSET', 'Bank', 1, 0);
('ASSET', 'Bank', 1, 1, 0);
INSERT INTO `pmgr_accounts` (`type`, `name`, `trackable`)
VALUES
('EXPENSE', 'Bad Debt', 0),
@@ -970,89 +968,139 @@ DROP TABLE IF EXISTS `pmgr_transactions`;
CREATE TABLE `pmgr_transactions` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
-- `type` ENUM('INVOICE',
-- 'RECEIPT')
-- NOT NULL,
`type` ENUM('INVOICE',
'CREDIT',
'RECEIPT',
'REFUND',
'TRANSFER')
NOT NULL,
`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,
-- REVISIT <AP>: 20090604
-- How should we track which charges have been paid?
-- `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 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;
@@ -1077,6 +1125,237 @@ CREATE TABLE `pmgr_monetary_sources` (
) 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' };
$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'}),
'moveout_date' => datefmt($row->{'DateOut'}),
'close_date' => datefmt($row->{'DateClosed'}),
'rent' => $row->{'Rent'} });
'rent' => $row->{'Rent'},
'comment' => "LedgerID: $row->{'LedgerID'}",
});
$newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'} =
$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'});
addRow('transactions',
{ 'stamp' => $stamp,
{ 'type' => 'INVOICE',
'stamp' => $stamp,
#'amount' => $row->{'InvoiceAmount'},
#'comment' => "Invoice Transaction",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}
= { 'invoice' =>
{ 'tx' => $newdb{'tables'}{'transactions'}{'autoid'},
'lease_id' => $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'},
'customer_id' => $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'},
'amount' => $row->{'InvoiceAmount'},
} };
= { 'invoice' => {'id' => $newdb{'tables'}{'transactions'}{'autoid'},
'amount' => $row->{'InvoiceAmount'} },
};
# Charges are the only way we have to figure out security
# deposit requirements for a lease. So, if we encounter
@@ -947,51 +932,6 @@ foreach $row (@{query($sdbh, $query)}) {
]{'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";
foreach $row (@{query($sdbh, $query)}) {
my (undef, $effective_date, $through_date) =
dates('charge', $row->{'ChargeDate'}, $row->{'EndDate'},
$row->{'ChargeDescription'}, $row->{'LedgerID'});
my (undef, $effective_date, $through_date) =
dates('charge', $row->{'ChargeDate'}, $row->{'EndDate'},
$row->{'ChargeDescription'}, $row->{'LedgerID'});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tx'}
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'}
= $row->{'ChargeAmount'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'},
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'lease_id'};
# 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_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'}}{'invoice'}{'credit_ledger_id'};
= $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
# Add the charge entry
# debit: Invoice credit: Rent/LateCharge/Etc
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'}}{'tx'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'amount' => $row->{'ChargeAmount'},
#'comment' => "Charge: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}",
# debit: A/R credit: Rent/LateCharge/Etc
addRow('double_entries',
{ 'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'id'},
'effective_date' => $effective_date,
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'credit_ledger_id'},
'amount' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'},
'comment' => "Double Entry: $row->{'ChargeID'}; Ledger: $row->{'LedgerID'}",
});
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'double_entry_id'}
= $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{'tables'}{'ledger_entries'}{'autoid'};
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ar_entry_id'}
= $newdb{'tables'}{'entries'}{'autoid'};
if ($use_invoice) {
# Reconcile the Invoice account. Our two entries look like:
# 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'};
}
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'}
= $row->{'ChargeAmount'};
next unless $row->{'TaxAmount'};
# Add the tax charge entry
# debit: Invoice credit: Tax
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'}}{'tx'},
'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Tax'}{'ledger_id'},
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
'lease_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'},
'amount' => $row->{'TaxAmount'},
#'comment' => "Tax for ChargeID:$row->{'ChargeID'}",
});
# # Add the tax charge entry
# # debit: Invoice credit: Tax
# addRow('ledger_entries',
# { 'effective_date' => $effective_date,
# 'through_date' => $through_date,
# 'transaction_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'id'},
# 'debit_ledger_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'debit_ledger_id'},
# 'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Tax'}{'ledger_id'},
# 'amount' => $row->{'TaxAmount'},
# #'comment' => "Tax for ChargeID:$row->{'ChargeID'}",
# });
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tax_ledger_entry_id'}
= $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'},
});
}
# $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tax_ledger_entry_id'}
# = $newdb{'tables'}{'ledger_entries'}{'autoid'};
}
######################################################################
######################################################################
######################################################################
######################################################################
######################################################################
######################################################################
##
## RECEIPTS
##
######################################################################
## Receipts
@@ -1186,190 +1129,188 @@ $query =
" WHERE P.ReceiptNum = R.ReceiptNum" .
" GROUP BY R.ReceiptNum, R.ReceiptDate, P.PaymentType, P.CheckNum" .
" ORDER BY R.ReceiptNum, P.PaymentType";
# print Dumper query($sdbh, $query);
# exit;
foreach $row (@{query($sdbh, $query)}) {
# if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'}) {
# next;
# }
my ($stamp, $effective_date, $through_date) =
dates('receipt', $row->{'ReceiptDate'}, undef);
my ($stamp, $effective_date, $through_date) =
dates('receipt', $row->{'ReceiptDate'}, undef);
if (!$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}) {
addRow('transactions',
{ 'stamp' => $stamp,
#'comment' => "Receipt Transaction",
});
$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'};
if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}) {
print Dumper $newdb{'lookup'}{'receipt'};
print Dumper $row;
die "REALLY?";
}
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}
= { 'date' => $stamp,
'amount' => $row->{'ReceiptAmount'},
};
if ($row->{'ReceiptDate'} =~ m%3/25/2009%) {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}
= $newdb{'ids'}{'monetary_source'}{'Closing'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'}
= 'Closing';
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
= 'Bank';
}
else {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}
= $newdb{'ids'}{'monetary_source'}{
$newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'}
};
else {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$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'}{'payment_type'}{$row->{'PaymentType'}}{'account_name'};
}
# Set up a monetary source for the receipt,
if (!$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}) {
my $name = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'};
my $data1;
if ($name eq 'Check') {
$name = 'Check #' . $row->{'CheckNum'};
$data1 = $row->{'CheckNum'};
if ($newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'} eq 'Check') {
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'name'}
= 'Check #' . $row->{'CheckNum'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'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)...
$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'}{'account'}{
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'account_name'}
}{'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'}{'account'}{'Receipt'}{'ledger_id'};
= $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
# NOTE THE ABOVE LARGE COMMENT BLOCK
# The choice of credit/debit ledgers does not mirror the
# choices for invoice. This _should_ be A/R to Receipt,
# but it is Money to Receipt instead.
# debit: Cash/Check/Etc credit: Receipt
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'},
'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'}; ",
});
# debit: Cash/Check/Etc credit: A/R
addRow('double_entries',
{ 'transaction_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'receipt_id'},
'effective_date' => $effective_date,
'customer_id' => undef, # This is set later...
'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' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'amount'},
'comment' => "Double Entry Receipt: $row->{'ReceiptNum'}; Type: $row->{'PaymentType'}",
});
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'ledger_entry_id'}
= $newdb{'tables'}{'ledger_entries'}{'autoid'};
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'}
= $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'} = {};
$query = "SELECT * FROM Payments ORDER BY PaymentID";
foreach $row (@{query($sdbh, $query)})
{
my (undef, $effective_date, $through_date) =
dates('payment', $row->{'PaymentDate'}, undef);
$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'},
};
# NOTE THE ABOVE LARGE COMMENT BLOCK
# The choice of credit/debit ledgers does not mirror the
# 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'}
# Ensure Payment has the right customer
$newdb{'tables'}{'double_entries'}{'rows'}[
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'double_entry_id'}
]{'customer_id'} = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'};
# Payment must debit the associated receipt ledger (which should be Receipt)...
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'debit_ledger_id'}
= $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'},
});
next
if ($newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'reconciled'});
# Figure out how much of the charge can be reconciled
my $reconcile_amount = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'amount'};
#print Dumper($newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}); exit;
$reconcile_amount = $row->{'PaymentAmount'} if $row->{'PaymentAmount'} <= $reconcile_amount;
my $charge_amount = $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'amount'};
my $payment_amount = $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'amount'};
# Reconcile the A/R account. Our two entries look like:
# debit: Receipt credit: A/R
# debit: A/R credit: Invoice
# 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.
my $reconcile_amount = ($charge_amount < $payment_amount) ? $charge_amount : $payment_amount;
# Reconcile the A/R Account
addRow('reconciliations',
{ 'debit_ledger_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'ledger_entry_id'},
'credit_ledger_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ledger_entry_id'},
'amount' => $reconcile_amount,
});
{ 'debit_entry_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'ar_entry_id'},
'credit_entry_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'ar_entry_id'},
'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
if ($row->{'Memo'}) {
my $txid = $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'tx'};
$newdb{'tables'}{'transactions'}{'rows'}[$txid]{'comment'} = $row->{'Memo'};
my $id = $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'entry_id'};
$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
@@ -1380,57 +1321,89 @@ print("Set up Petty Cash...\n");
# Add the first loan
# debit: Equity credit: Loan
addRow('transactions',
{ 'stamp' => datefmt('03/25/2009 16:00'),
});
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'customer_id' => undef,
'lease_id' => undef,
'amount' => 5000,
'comment' => "HTP Loan #1",
});
{ 'type' => 'TRANSFER',
'stamp' => datefmt('03/25/2009 16:00'),
});
addRow('double_entries',
{ 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'effective_date' => $effective_date,
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'amount' => 5000,
'comment' => "HTP Loan #1",
});
addRow('entries',
{ '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
# debit: Equity credit: Loan
addRow('transactions',
{ 'stamp' => datefmt('04/01/2009 16:00'),
});
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'customer_id' => undef,
'lease_id' => undef,
'amount' => 1000,
'comment' => "HTP Loan #2",
});
{ 'type' => 'TRANSFER',
'stamp' => datefmt('04/01/2009 16:00'),
});
addRow('double_entries',
{ 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'effective_date' => $effective_date,
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Loan'}{'ledger_id'},
'amount' => 1000,
'comment' => "HTP Loan #2",
});
addRow('entries',
{ '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
# debit: Petty Cash credit: Equity
addRow('transactions',
{ 'stamp' => datefmt('03/25/2009 16:00'),
});
addRow('ledger_entries',
{ 'effective_date' => $effective_date,
'through_date' => $through_date,
'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Petty Cash'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'customer_id' => undef,
'lease_id' => undef,
'amount' => 750,
'comment' => "Petty Cash Funding",
});
{ 'type' => 'TRANSFER',
'stamp' => datefmt('03/25/2009 16:00'),
});
addRow('double_entries',
{ 'transaction_id' => $newdb{'tables'}{'transactions'}{'autoid'},
'effective_date' => $effective_date,
'debit_ledger_id' => $newdb{'lookup'}{'account'}{'Petty Cash'}{'ledger_id'},
'credit_ledger_id' => $newdb{'lookup'}{'account'}{'Equity'}{'ledger_id'},
'amount' => 750,
'comment' => "Petty Cash Funding",
});
addRow('entries',
{ '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
@@ -1492,3 +1465,14 @@ $query = "UPDATE pmgr_units U, pmgr_leases L
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 {
var $uses = array('Account', 'LedgerEntry');
var $uses = array('Account', 'DoubleEntry');
var $sidemenu_links =
array(array('name' => 'Accounts', 'header' => true),
@@ -68,40 +68,15 @@ class AccountsController extends AppController {
array(// Models
'CurrentLedger' => array
(// Models
'LedgerEntry'
/* 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
*/
'DoubleEntry'
),
),
);
}
function jqGridDataFields(&$params, &$model) {
return array
('Account.*',
'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');
return array_merge(array('Account.*'),
$this->Account->Ledger->DoubleEntry->debitCreditFields('DoubleEntry', 'CurrentLedger'));
}
function jqGridDataConditions(&$params, &$model) {
@@ -191,7 +166,7 @@ class AccountsController extends AppController {
('Account' =>
array('fields' => array('name')),
'LedgerEntry' =>
'DoubleEntry' =>
array('fields' => array('id', 'amount'),
'MonetarySource' =>
@@ -206,7 +181,7 @@ class AccountsController extends AppController {
),
'fields' => false,
'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
$entries = $this->Account->findLedgerEntries($id);
$account['CurrentLedger']['LedgerEntry'] = $entries['Entries'];
$account['CurrentLedger']['DoubleEntry'] = $entries['Entries'];
// Summarize each ledger
foreach($account['Ledger'] AS &$ledger)
@@ -294,4 +269,8 @@ class AccountsController extends AppController {
$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) {
// 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();
$lease = $this->Lease->find
@@ -351,6 +401,18 @@ class LeasesController extends AppController {
*/
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' =>
array(// Models
'Account',
'LedgerEntry',
'DoubleEntry',
'Close',
),
);
}
function jqGridDataFields(&$params, &$model) {
return array
('Ledger.*',
'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence',
'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');
return array_merge(array('Ledger.*',
'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence'),
$this->Ledger->DoubleEntry->debitCreditFields());
}
function jqGridDataConditions(&$params, &$model) {

View File

@@ -1,13 +1,6 @@
<?php
class Account extends AppModel {
var $name = 'Account';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
'external_name' => array('notempty')
);
var $hasOne = array(
'CurrentLedger' => array(
'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
* 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', */
/* 'args' => compact('id', 'all', 'cond', 'link'), */
/* )); */
$entries = array();
foreach ($this->ledgers($id, $all) AS $ledger_id) {
$ledger_entries = $this->Ledger->findLedgerEntries
($ledger_id, $this->type($id), $cond, $link);
$entries = array_merge($entries, $ledger_entries);
}
/* $this->Entry->find */
/* ('all', array */
/* ('contain' => array(), */
/* 'conditions' => array('Entry.account_id' => $id) */
/* )); */
$stats = $this->stats($id, $all, $cond);
$entries = array('Entries' => $entries,
'summary' => $stats['Ledger']);
$ledgers = $this->ledgers($id, $all);
/* $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', */
/* 'args' => compact('id', 'all', 'cond', 'link'), */
@@ -379,7 +393,7 @@ class Account extends AppModel {
foreach ($rel_ids AS $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);
/* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */
@@ -413,28 +427,28 @@ class Account extends AppModel {
('link' => array
('Ledger' => array
('fields' => array(),
"LedgerEntry" => array
('class' => "{$ucfund}LedgerEntry",
"DoubleEntry" => array
('class' => "{$ucfund}DoubleEntry",
'fields' => array('id', 'customer_id', 'lease_id', 'amount'),
"ReconciliationLedgerEntry" => array
('class' => "{$ucfund}ReconciliationLedgerEntry",
"ReconciliationDoubleEntry" => array
('class' => "{$ucfund}ReconciliationDoubleEntry",
'fields' => array
("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" .
" HAVING LedgerEntry.amount" .
'group' => ("DoubleEntry.id" .
" HAVING DoubleEntry.amount" .
" <> COALESCE(SUM(Reconciliation.amount),0)"),
'conditions' => $cond,
'fields' => array(),
));
$balance = 0;
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]);
$balance += $entry['balance'];
}
@@ -448,7 +462,7 @@ class Account extends AppModel {
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcileNewLedgerEntry
* function: amountWouldReconcile
* - Returns which ledger entries a new credit/debit would
* reconcile, and how much.
*
@@ -459,7 +473,7 @@ class Account extends AppModel {
* 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);
$unreconciled = array($ofund => array('entry'=>array(), 'balance'=>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_id (optional... if set all else is ignored)
@@ -513,7 +598,7 @@ class Account extends AppModel {
* - name
*/
function postLedgerEntry($transaction_data,
function postDoubleEntry($transaction_data,
$monetary_data,
$entry_data,
$reconcile = null) {
@@ -566,7 +651,7 @@ class Account extends AppModel {
// No distinguishing features of Cash, just
// use the shared monetary source
$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')));
// Create it!
$new_entry = new LedgerEntry();
$new_entry = new DoubleEntry();
$new_entry->create();
if (!$new_entry->saveAll($entry_data, array('validate'=>false))) {
return array('error' => true);
@@ -664,9 +749,9 @@ class Account extends AppModel {
if ($reconcile_set === 'receipt') {
$C = new Customer();
$reconciled = $C->reconcileNewLedgerEntry($entry_data['customer_id'],
$this->fundamentalOpposite($dc_type),
$entry_data['amount']);
$reconciled = $C->amountWouldReconcile($entry_data['customer_id'],
$this->fundamentalOpposite($dc_type),
$entry_data['amount']);
/* pr(array("reconcile receipt", */
/* 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
// debit: Receipt credit: A/R
$ids = $this->postLedgerEntry
$ids = $this->postDoubleEntry
($split_transaction,
null,
array('debit_ledger_id' => $this->currentLedgerID($this->receiptAccountID()),
@@ -698,9 +783,9 @@ class Account extends AppModel {
'lease_id' => $rec['lease_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']))),
'credit' => array(array('LedgerEntry' => array('id' => $rec['id'],
'credit' => array(array('DoubleEntry' => array('id' => $rec['id'],
'amount' => $rec['applied']))))
);
// Keep using the same split transaction for all reconciled entries
@@ -717,19 +802,19 @@ class Account extends AppModel {
if (is_array($reconcile_set)) {
//pr("reconcile_set is array");
foreach ($reconcile_set AS $reconcile_entry) {
if (!isset($reconcile_entry['LedgerEntry']['id']))
if (!isset($reconcile_entry['DoubleEntry']['id']))
continue;
$amount = $reconcile_entry['LedgerEntry']['amount'];
$amount = $reconcile_entry['DoubleEntry']['amount'];
if (!$amount)
continue;
if ($dc_type == 'debit') {
$debit_ledger_entry_id = $new_entry->id;
$credit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
$credit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
}
else {
$debit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
$debit_ledger_entry_id = $reconcile_entry['DoubleEntry']['id'];
$credit_ledger_entry_id = $new_entry->id;
}
@@ -749,9 +834,9 @@ class Account extends AppModel {
$ret = array
('error' => $err,
'id' => $new_entry->data['LedgerEntry']['id'],
'transaction_id' => $new_entry->data['LedgerEntry']['transaction_id'],
'monetary_source_id' => $new_entry->data['LedgerEntry']['monetary_source_id']);
'id' => $new_entry->data['DoubleEntry']['id'],
'transaction_id' => $new_entry->data['DoubleEntry']['transaction_id'],
'monetary_source_id' => $new_entry->data['DoubleEntry']['monetary_source_id']);
if (isset($split_transaction['transaction_id']))
$ret['split_transaction_id'] = $split_transaction['transaction_id'];
@@ -783,7 +868,7 @@ class Account extends AppModel {
if ($ledger['total'] == 0)
continue;
$ids = $this->postLedgerEntry
$ids = $this->postDoubleEntry
($transaction,
null,
array('debit_account_id' => $deposit_account_id,
@@ -795,7 +880,7 @@ class Account extends AppModel {
//pr(compact('ids'));
if ($ids['error'])
die("closeAndDeposit : postLedgerEntry returned error!");
die("closeAndDeposit : postDoubleEntry returned error!");
$transaction = array_intersect_key($ids, array('transaction_id'=>1));

View File

@@ -86,7 +86,7 @@ class LinkableBehavior extends ModelBehavior {
protected $_defaults = array('type' => 'LEFT');
function pr($lev, $mixed) {
if ($lev >= 5)
if ($lev >= 3)
return;
pr($mixed);
@@ -159,7 +159,7 @@ class LinkableBehavior extends ModelBehavior {
$iterations = Set::normalize($iterator);
$this->pr(25,
array('checkpoint' => 'Iterations',
compact('iterations'),
compact('cont', 'iterator', 'iterations'),
));
foreach ($iterations as $alias => $options) {
if (is_null($options)) {
@@ -246,15 +246,32 @@ class LinkableBehavior extends ModelBehavior {
else
$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}",
$associationAlias,
$association['conditions']);
$options['conditions']);
$this->pr(15,
array('checkpoint' => 'Models Established - Check Associations',
'primaryModel' => $primaryAlias .' : '. $primaryModel->name,
'foreignModel' => $foreignAlias .' : '. $foreignModel->name,
compact('type', 'association'),
compact('type', 'association', 'options'),
));
if ($type === 'hasAndBelongsToMany') {
@@ -270,16 +287,29 @@ class LinkableBehavior extends ModelBehavior {
else
$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,
array('checkpoint' => 'Linking HABTM',
compact('linkClass', 'linkAlias'),
compact('linkClass', 'linkAlias',
'modelAFK', 'referenceAFK'),
));
// Get the foreign key fields (for the link table) directly from
// the defined model associations, if they exists. This is the
// users direct specification, and therefore definitive if present.
$modelLink = $Link->escapeField($association['foreignKey'], $linkAlias);
$referenceLink = $Link->escapeField($association['associationForeignKey'], $linkAlias);
$modelLink = $Link->escapeField($association[$modelAFK], $linkAlias);
$referenceLink = $Link->escapeField($association[$referenceAFK], $linkAlias);
// 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
@@ -300,6 +330,12 @@ class LinkableBehavior extends ModelBehavior {
$referenceKey = $Reference->escapeField(null, $referenceAlias);
$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,
// as the whole purpose of the linkage table is to make this
// connection. As we are embedding this join, the INNER will not
@@ -345,20 +381,6 @@ class LinkableBehavior extends ModelBehavior {
$this->pr(19,
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'],
),
));
@@ -372,13 +394,16 @@ class LinkableBehavior extends ModelBehavior {
elseif (!empty($options['fields']))
$options['fields'] = $db->fields($_Model, $modelAlias, $options['fields']);
$query['fields'] = array_merge($query['fields'], $options['fields'],
(empty($association['fields'])
? array() : $db->fields($_Model, $modelAlias, $association['fields'])));
$query['fields'] = array_merge($query['fields'], $options['fields']);
$options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys));
$options = array_intersect_key($options, $optionsKeys);
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] +
array('defaults' =>
array_merge($defaults,

View File

@@ -19,17 +19,14 @@ class Customer extends AppModel {
'conditions' => 'CurrentLease.close_date IS NULL',
),
'Lease',
'LedgerEntry',
'DoubleEntry',
'ContactsCustomer',
'Transaction',
);
var $hasAndBelongsToMany = array(
'Contact',
'Transaction' => array(
'joinTable' => 'ledger_entries',
'foreignKey' => 'customer_id',
'associationForeignKey' => 'transaction_id',
),
);
@@ -87,7 +84,7 @@ class Customer extends AppModel {
$A = new Account();
$entries = $A->findLedgerEntries
($A->securityDepositAccountID(),
true, array('LedgerEntry.customer_id' => $id), $link);
true, array('DoubleEntry.customer_id' => $id), $link);
/* pr(array('function' => 'Customer::findSecurityDeposits', */
/* 'args' => compact('id', 'link'), */
@@ -112,7 +109,7 @@ class Customer extends AppModel {
$unreconciled = $A->findUnreconciledLedgerEntries
($A->accountReceivableAccountID(),
$fundamental_type,
array('LedgerEntry.customer_id' => $id));
array('DoubleEntry.customer_id' => $id));
return $unreconciled;
}
@@ -121,7 +118,7 @@ class Customer extends AppModel {
/**************************************************************************
**************************************************************************
**************************************************************************
* function: reconcileNewLedgerEntry
* function: amountWouldReconcile
* - Returns which ledger entries a new credit/debit would
* reconcile, and how much.
*
@@ -132,18 +129,41 @@ class Customer extends AppModel {
* whatever algorithm is simplest.
*/
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
function amountWouldReconcile($id, $fundamental_type, $amount) {
$A = new Account();
$reconciled = $A->reconcileNewLedgerEntry
$reconciled = $A->amountWouldReconcile
($A->accountReceivableAccountID(),
$fundamental_type,
$amount,
array('LedgerEntry.customer_id' => $id));
array('DoubleEntry.customer_id' => $id));
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();
$stats = $A->stats($A->accountReceivableAccountID(), true,
array('LedgerEntry.customer_id' => $id));
array('DoubleEntry.customer_id' => $id));
// Pull to the top level and return
$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
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(
'LeaseType',
'Unit',
@@ -30,7 +9,7 @@ class Lease extends AppModel {
);
var $hasMany = array(
'LedgerEntry',
'DoubleEntry',
);
@@ -60,7 +39,7 @@ class Lease extends AppModel {
if (!isset($cond))
$cond = array();
$cond[] = array('LedgerEntry.lease_id' => $id);
$cond[] = array('DoubleEntry.lease_id' => $id);
$A = new Account();
$entries = $A->findLedgerEntries($this->accountId($id),
@@ -89,7 +68,7 @@ class Lease extends AppModel {
$A = new Account();
$entries = $A->findLedgerEntries
($A->securityDepositAccountID(),
true, array('LedgerEntry.lease_id' => $id), $link);
true, array('DoubleEntry.lease_id' => $id), $link);
/* pr(array('function' => 'Lease::findSecurityDeposits', */
/* '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',
array('link' =>
array(// Models
'LedgerEntry' => array
'DoubleEntry' => array
('Ledger' => array
('fields' => array(),
'Account' => array
@@ -161,12 +104,12 @@ class Lease extends AppModel {
'Ledger' => array
('alias' => 'Lx',
'fields' => array(),
'LedgerEntry' => array
'DoubleEntry' => array
('alias' => 'LEx',
'fields' => array(),
'conditions' => array
('LEx.effective_date = DATE_ADD(LedgerEntry.through_date, INTERVAL 1 day)',
'LEx.lease_id = LedgerEntry.lease_id',
('LEx.effective_date = DATE_ADD(DoubleEntry.through_date, INTERVAL 1 day)',
'LEx.lease_id = DoubleEntry.lease_id',
)
),
),
@@ -219,7 +162,7 @@ class Lease extends AppModel {
return false;
if (count($entries) != 1)
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
// debit: A/R credit: Income <-- this entry
// debit: Receipt credit: A/R <-- ReceiptLedgerEntry, below
// debit: Money credit: Receipt <-- MoneyLedgerEntry, below
// debit: Receipt credit: A/R <-- ReceiptDoubleEntry, below
// debit: Money credit: Receipt <-- MoneyDoubleEntry, below
$query = array
('link' => array
@@ -250,43 +193,43 @@ class Lease extends AppModel {
// 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.
'DebitReconciliationLedgerEntry' =>
array('alias' => 'ReceiptLedgerEntry',
'DebitReconciliationDoubleEntry' =>
array('alias' => 'ReceiptDoubleEntry',
'fields' => array(),
// Finally, the Money (Cash/Check/etc) Entry is the one
// which reconciles our ReceiptLedgerEntry debit
'DebitReconciliationLedgerEntry' =>
array('alias' => 'MoneyLedgerEntry',
'linkalias' => 'MoneyLedgerEntryR',
'fields' => array('SUM(COALESCE(MoneyLedgerEntryR.amount,0)) AS paid'),
// which reconciles our ReceiptDoubleEntry debit
'DebitReconciliationDoubleEntry' =>
array('alias' => 'MoneyDoubleEntry',
'linkalias' => 'MoneyDoubleEntryR',
'fields' => array('SUM(COALESCE(MoneyDoubleEntryR.amount,0)) AS paid'),
),
),
),
'fields' => array('LedgerEntry.amount',
'DATE_SUB(LedgerEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
'fields' => array('DoubleEntry.amount',
'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),
array('Account.id' => $this->LedgerEntry->Ledger->Account->rentAccountID()),
'conditions' => array(array('DoubleEntry.lease_id' => $id),
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)
return $rent[0]['paid_through'];
$query['fields'] = 'LedgerEntry.through_date';
$query['order'] = 'LedgerEntry.through_date DESC';
$query['group'] = 'LedgerEntry.id';
$rent = $this->LedgerEntry->find('first', $query);
$query['fields'] = 'DoubleEntry.through_date';
$query['order'] = 'DoubleEntry.through_date DESC';
$query['group'] = 'DoubleEntry.id';
$rent = $this->DoubleEntry->find('first', $query);
if ($rent)
return $rent['LedgerEntry']['through_date'];
return $rent['DoubleEntry']['through_date'];
return null;
}
@@ -495,7 +438,7 @@ class Lease extends AppModel {
$A = new Account();
$stats = $A->stats($A->accountReceivableAccountID(), true,
array('LedgerEntry.lease_id' => $id));
array('DoubleEntry.lease_id' => $id));
// Pull to the top level and return
$stats = $stats['Ledger'];

View File

@@ -1,12 +1,6 @@
<?php
class Ledger extends AppModel {
var $name = 'Ledger';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
);
var $belongsTo = array(
'Account',
'PriorLedger' => array('className' => 'Ledger'),
@@ -14,32 +8,33 @@ class Ledger extends AppModel {
);
var $hasMany = array(
'LedgerEntry' => array(
'DoubleEntry' => array(
'foreignKey' => false,
// conditions will be used when JOINing tables
// (such as find with LinkableBehavior)
'conditions' => array('OR' =>
array('LedgerEntry.debit_ledger_id = %{MODEL_ALIAS}.id',
'LedgerEntry.credit_ledger_id = %{MODEL_ALIAS}.id')),
array('DoubleEntry.debit_ledger_id = %{MODEL_ALIAS}.id',
'DoubleEntry.credit_ledger_id = %{MODEL_ALIAS}.id')),
// finderQuery will be used when tables are put
// together across several querys, not with JOIN.
// (such as find with ContainableBehavior)
'finderQuery' => 'SELECT `LedgerEntry`.*
FROM pmgr_ledger_entries AS `LedgerEntry`
WHERE LedgerEntry.debit_ledger_id = ({$__cakeID__$})
OR LedgerEntry.credit_ledger_id = ({$__cakeID__$})',
'finderQuery' => 'SELECT `DoubleEntry`.*
FROM pmgr_double_entries AS `DoubleEntry`
WHERE DoubleEntry.debit_ledger_id = ({$__cakeID__$})
OR DoubleEntry.credit_ledger_id = ({$__cakeID__$})',
'counterQuery' => ''
),
'DebitLedgerEntry' => array(
'className' => 'LedgerEntry',
'className' => 'DoubleEntry',
'foreignKey' => 'debit_ledger_id',
'dependent' => false,
),
'CreditLedgerEntry' => array(
'className' => 'LedgerEntry',
'className' => 'DoubleEntry',
'foreignKey' => 'credit_ledger_id',
'dependent' => false,
),
@@ -124,7 +119,7 @@ class Ledger extends AppModel {
'comment' => "Ledger Balance Forward",
);
$carry_entry = new LedgerEntry();
$carry_entry = new DoubleEntry();
$carry_entry->create();
if (!$carry_entry->save($carry_entry_data, false)) {
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
* 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', */
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
/* )); */
@@ -177,7 +172,7 @@ class Ledger extends AppModel {
'credit' => $stats['credits'],
'balance' => $stats['balance']),
'LedgerEntry' => array('id' => null,
'DoubleEntry' => array('id' => null,
//'comment' => "Balance Forward from $date"),
'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', */
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
/* 'vars' => compact('ledger'), */
@@ -214,23 +209,23 @@ class Ledger extends AppModel {
('link' =>
array(// Models
'Account' => array('fields' => array()),
//'LedgerEntry' => array('fields' => array()),
'LedgerEntry' =>
//'DoubleEntry' => array('fields' => array()),
'DoubleEntry' =>
array('fields' => array(),
'Transaction' => array('fields' => array('stamp')),
),
),
'fields' =>
array("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",
array("SUM(IF(DoubleEntry.debit_ledger_id = Ledger.id,
DoubleEntry.amount, NULL)) AS debits",
"SUM(IF(DoubleEntry.credit_ledger_id = Ledger.id,
DoubleEntry.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)
IF(DoubleEntry.debit_ledger_id = Ledger.id, 1, -1),
IF(DoubleEntry.credit_ledger_id = Ledger.id, 1, -1)
) * IF(DoubleEntry.amount, DoubleEntry.amount, 0)
) AS balance",
"COUNT(LedgerEntry.id) AS entries"),
"COUNT(DoubleEntry.id) AS entries"),
'conditions' => $cond,
'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
class MonetarySource extends AppModel {
var $name = 'MonetarySource';
var $validate = array(
'id' => array('numeric'),
'name' => array('notempty'),
'tillable' => array('boolean')
);
class Payment extends AppModel {
var $belongsTo = array(
'Customer',
'Lease',
);
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) {
pr(array('MonetarySource::nsf',
pr(array('Payment::nsf',
compact('id')));
$A = new Account();
@@ -58,9 +97,9 @@ class MonetarySource extends AppModel {
('first',
array('contain' =>
array(/* e3 */
'LedgerEntry' =>
'DoubleEntry' =>
array('Transaction.id',
'MonetarySource.id',
'Payment.id',
'Customer.id',
'Lease.id',
@@ -87,7 +126,7 @@ class MonetarySource extends AppModel {
),
/* e2 */
'DebitReconciliationLedgerEntry' =>
'DebitReconciliationDoubleEntry' =>
array(/* e2 credit */
'CreditLedger' => /* i.e. A/R Ledger */
array('fields' => array(),
@@ -102,11 +141,11 @@ class MonetarySource extends AppModel {
/* e1 */
// STUPID CakePHP bug screws up CLASS contains CLASS.
// Use the same class, but with different name.
'DebitReconciliationLedgerEntry2',
'DebitReconciliationDoubleEntry2',
),
/* e4 */
'CreditReconciliationLedgerEntry' =>
'CreditReconciliationDoubleEntry' =>
array(/* e4 debit */
'DebitLedger' => /* e.g. BANK Ledger */
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);
@@ -131,9 +170,9 @@ class MonetarySource extends AppModel {
$t4_id = null;
$t5_id = null;
foreach ($source['LedgerEntry'] AS $e3) {
foreach ($source['DoubleEntry'] AS $e3) {
// We expect only a single e4 entry
$e4 = $e3['CreditReconciliationLedgerEntry'];
$e4 = $e3['CreditReconciliationDoubleEntry'];
if (count($e4) < 1)
continue;
if (count($e4) > 1)
@@ -149,7 +188,7 @@ class MonetarySource extends AppModel {
$bank_account_id = $e4['DebitLedger']['account_id'];
// post new e5
$e5_ids = $A->postLedgerEntry
$e5_ids = $A->postDoubleEntry
(array('transaction_id' => $t4_id),
null,
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
// from typical positive entries to negative entries.
// Therefore, no reconciliation on this account.
$e6_ids = $A->postLedgerEntry
$e6_ids = $A->postDoubleEntry
(array('transaction_id' => $t5_id),
array('monetary_source_id' => $e3['monetary_source_id']),
array('debit_ledger_id' => $A->currentLedgerID($nsf_account_id),
@@ -184,7 +223,7 @@ class MonetarySource extends AppModel {
'comment' => "NSF tracker; Monetary Source #{$id}",
),
array('debit' => array
(array('LedgerEntry' =>
(array('DoubleEntry' =>
array('id' => $e5_ids['id'],
'amount' => $amount))),
)
@@ -198,12 +237,12 @@ class MonetarySource extends AppModel {
compact('e6_ids', 'amount')));
$t6_id = null;
foreach ($e3['DebitReconciliationLedgerEntry'] AS $e2) {
foreach ($e2['DebitReconciliationLedgerEntry2'] AS $e1) {
foreach ($e3['DebitReconciliationDoubleEntry'] AS $e2) {
foreach ($e2['DebitReconciliationDoubleEntry2'] AS $e1) {
$amount = -1*$e1['Reconciliation']['amount'];
// post new e7
$e7_ids = $A->postLedgerEntry
$e7_ids = $A->postDoubleEntry
(array('transaction_id' => $t6_id),
null,
array('debit_ledger_id' => $A->currentLedgerID($receipt_account_id),
@@ -215,12 +254,12 @@ class MonetarySource extends AppModel {
'comment' => "NSF Receipt; Monetary Source #{$id}",
),
array('debit' => array
(array('LedgerEntry' =>
(array('DoubleEntry' =>
array('id' => $e6_ids['id'],
'amount' => $amount))),
'credit' => array
(array('LedgerEntry' =>
(array('DoubleEntry' =>
array('id' => $e1['id'],
'amount' => $amount))),
)
@@ -237,11 +276,11 @@ class MonetarySource extends AppModel {
}
// Cheat for now
$customer_id = $source['LedgerEntry'][0]['customer_id'];
$customer_id = $source['DoubleEntry'][0]['customer_id'];
$lease_id = null;
// post new e8
$e8_ids = $A->postLedgerEntry
$e8_ids = $A->postDoubleEntry
(array('transaction_id' => $t6_id),
null,
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),

View File

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

View File

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

View File

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

View File

@@ -109,15 +109,20 @@ if (!isset($config['rows']) && !isset($collected_account_id)) {
}
if (isset($reconcile_id)) {
$config['action'] = 'reconcile';
$grid->customData(compact('reconcile_id'))->limit(20);
}
if (isset($collected_account_id)) {
$config['action'] = 'collected';
$grid->customData(compact('collected_account_id'))->limit(50);
$account_id = $collected_account_id;
$grid->limit(50);
$grid->sortField('Last Payment');
}
if (isset($entry_ids))
$grid->id_list($entry_ids);
// Set up search fields if requested by caller
if (isset($searchfields))
$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',
'return $data["id"];'),
$items);
$this->jqGrid_options['action'] = 'idlist';
return $this;
}

View File

@@ -19,9 +19,11 @@ echo ('<DIV CLASS="apply-deposit grid-selection-text">' .
'</DIV>' . "\n");
echo $form->create(null, array('id' => 'receipt-form',
'url' => array('controller' => 'transactions',
'action' => 'postReceipt')));
echo $form->create(null, array('id' => 'apply-deposit-form',
/* 'url' => array('controller' => 'lease', */
/* 'action' => 'apply_deposit') */
)
);
echo $form->input("Customer.id",
array('id' => 'customer-id',
@@ -33,7 +35,7 @@ echo $form->input("Lease.id",
'type' => 'hidden',
'value' => $lease['id']));
echo $form->input("LedgerEntry.0.account_id",
echo $form->input("LedgerEntry.Account.id",
array('id' => 'account-id',
'type' => 'hidden',
'value' => $account['id']));
@@ -47,7 +49,10 @@ echo $this->element('form_table',
("stamp" => array('opts' => array('type' => 'text'),
'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),
),
)));

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";