Compare commits
118 Commits
invoice_re
...
statement_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae09d160bb | ||
|
|
4ee9c99e30 | ||
|
|
cb716b06b7 | ||
|
|
35d7656bc5 | ||
|
|
8818ad7a80 | ||
|
|
15a4528e75 | ||
|
|
44e4477d38 | ||
|
|
c4cc3ea812 | ||
|
|
1e10fbbf38 | ||
|
|
53a279a6db | ||
|
|
8e0270cc82 | ||
|
|
3a95a994cf | ||
|
|
00c99ea60a | ||
|
|
e09fb7d258 | ||
|
|
37e7212abe | ||
|
|
655e0c3940 | ||
|
|
e47a2cc7aa | ||
|
|
0cdcb6252e | ||
|
|
bbec6ccbb6 | ||
|
|
91fdef9997 | ||
|
|
3e2d219c8d | ||
|
|
57d593fc38 | ||
|
|
65b7137f21 | ||
|
|
323519ab75 | ||
|
|
a1a9c7800b | ||
|
|
fe17f87f7d | ||
|
|
6b9279f5b0 | ||
|
|
2882b1917c | ||
|
|
f707448b05 | ||
|
|
5247139fe8 | ||
|
|
d2a4021d6f | ||
|
|
b759f29d91 | ||
|
|
c0d26a8e95 | ||
|
|
a1f30804a7 | ||
|
|
55b3ec947e | ||
|
|
876d9b3d33 | ||
|
|
d33bf24958 | ||
|
|
424cb0ea4f | ||
|
|
63de30d392 | ||
|
|
07232c77d5 | ||
|
|
1e7557de71 | ||
|
|
d91957faed | ||
|
|
56e6aa1f34 | ||
|
|
6e759e8589 | ||
|
|
9733226dfd | ||
|
|
be2865b4d7 | ||
|
|
adddfecada | ||
|
|
55c5c9e9e6 | ||
|
|
42677ae2f4 | ||
|
|
ec8f540107 | ||
|
|
393c0dbb3f | ||
|
|
f8413b8784 | ||
|
|
e1217cd185 | ||
|
|
37d0fe7f3f | ||
|
|
13f97e5770 | ||
|
|
b789ed62b7 | ||
|
|
2ffe04e3e4 | ||
|
|
cdba179d79 | ||
|
|
5ba6438b77 | ||
|
|
a4459ef0de | ||
|
|
e7b71e0abb | ||
|
|
0c06ef6d71 | ||
|
|
0f42ad9b07 | ||
|
|
c20b287c53 | ||
|
|
ef4d8d4d30 | ||
|
|
e7a659a690 | ||
|
|
f6ee56501d | ||
|
|
826eb63da1 | ||
|
|
6cf6dda3f8 | ||
|
|
5446f6a266 | ||
|
|
b225775a91 | ||
|
|
a5565546d1 | ||
|
|
f0b65dcf74 | ||
|
|
ad1758a5dc | ||
|
|
b3389652fd | ||
|
|
ba62eed027 | ||
|
|
3dc751c863 | ||
|
|
68fe787209 | ||
|
|
d4292a85a0 | ||
|
|
f9635419dd | ||
|
|
d8f10bfd13 | ||
|
|
25b7da57f3 | ||
|
|
29fe265daa | ||
|
|
769c02a5f1 | ||
|
|
476a179e7b | ||
|
|
df8ebe45ef | ||
|
|
0bf458f243 | ||
|
|
18c4f5ea48 | ||
|
|
a0f33054cb | ||
|
|
0f00b42d1f | ||
|
|
1e0b96953e | ||
|
|
b408d86a98 | ||
|
|
e739282d17 | ||
|
|
c73016ecf2 | ||
|
|
122dfb10a0 | ||
|
|
9a32800170 | ||
|
|
04bc284a76 | ||
|
|
708759765f | ||
|
|
9dccbfeda8 | ||
|
|
f8c60ec265 | ||
|
|
234999b4d2 | ||
|
|
0ba5007438 | ||
|
|
8cf8f65474 | ||
|
|
c596dfa5f0 | ||
|
|
8331be454c | ||
|
|
8b65bc5159 | ||
|
|
634f0f5423 | ||
|
|
7aa026f4e0 | ||
|
|
93ebc450fe | ||
|
|
fd856323a5 | ||
|
|
59e6379977 | ||
|
|
ae5d4763f9 | ||
|
|
c002f3f3ba | ||
|
|
f8d4dcef94 | ||
|
|
a7671e76fe | ||
|
|
fc30dfa2e8 | ||
|
|
6ac0204baf | ||
|
|
e303898a95 |
347
db/schema.sql
347
db/schema.sql
@@ -842,12 +842,11 @@ CREATE TABLE `pmgr_accounts` (
|
||||
-- For LIABILITY, EQUITY, and INCOME, the opposite
|
||||
-- is true, with reconciliations posted, under
|
||||
-- normal circumstances, when a debit occurs.
|
||||
`trackable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
|
||||
`tillable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Does manager collect by hand?
|
||||
`depositable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Does this account receive deposits?
|
||||
`chargeable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for charges?
|
||||
`payable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for payments?
|
||||
`refundable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for refunds?
|
||||
-- `trackable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
|
||||
`deposits` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for deposits?
|
||||
`charges` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for charges?
|
||||
`payments` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for payments?
|
||||
`refunds` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0, -- Can be used for refunds?
|
||||
|
||||
-- Security Level
|
||||
`level` INT UNSIGNED DEFAULT 10,
|
||||
@@ -869,40 +868,39 @@ 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.
|
||||
('ASSET', 'NSF' ),
|
||||
('LIABILITY', 'A/P' );
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `tillable`, `payable`, `refundable`)
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `payments`, `refunds`)
|
||||
VALUES
|
||||
('ASSET', 'Cash', 1, 1, 1),
|
||||
('ASSET', 'Check', 1, 1, 0),
|
||||
('ASSET', 'Money Order', 1, 1, 0),
|
||||
('ASSET', 'ACH', 0, 1, 0),
|
||||
('EXPENSE', 'Concession', 0, 1, 0);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `refundable`, `depositable`)
|
||||
('ASSET', 'Cash', 1, 1),
|
||||
('ASSET', 'Check', 1, 0),
|
||||
('ASSET', 'Money Order', 1, 0),
|
||||
('ASSET', 'ACH', 1, 0),
|
||||
('ASSET', 'Closing', 0, 0), -- REVISIT <AP>: Temporary
|
||||
('EXPENSE', 'Concession', 1, 0);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `refunds`, `deposits`)
|
||||
VALUES
|
||||
-- REVISIT <AP>: 20090710 : We probably don't really want petty cash depositable.
|
||||
-- This is just for testing our deposit code
|
||||
('ASSET', 'Petty Cash', 1, 1);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `chargeable`, `trackable`)
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `charges`)
|
||||
VALUES
|
||||
('LIABILITY', 'Tax', 1, 1),
|
||||
('LIABILITY', 'Security Deposit', 1, 1),
|
||||
('INCOME', 'Rent', 1, 0),
|
||||
('INCOME', 'Late Charge', 1, 0),
|
||||
('INCOME', 'NSF Charge', 1, 0),
|
||||
('INCOME', 'Damage', 1, 0);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `depositable`, `trackable`)
|
||||
('LIABILITY', 'Tax', 1),
|
||||
('LIABILITY', 'Security Deposit', 1),
|
||||
('INCOME', 'Rent', 1),
|
||||
('INCOME', 'Late Charge', 1),
|
||||
('INCOME', 'NSF Charge', 1),
|
||||
('INCOME', 'Damage', 1);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `deposits`, `refunds`)
|
||||
VALUES
|
||||
('ASSET', 'Bank', 1, 0);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`, `trackable`)
|
||||
('ASSET', 'Bank', 1, 1);
|
||||
INSERT INTO `pmgr_accounts` (`type`, `name`)
|
||||
VALUES
|
||||
('EXPENSE', 'Bad Debt', 0),
|
||||
('EXPENSE', 'Maintenance', 0);
|
||||
('EXPENSE', 'Bad Debt' ),
|
||||
('EXPENSE', 'Maintenance' );
|
||||
UNLOCK TABLES;
|
||||
|
||||
|
||||
@@ -924,23 +922,18 @@ UNLOCK TABLES;
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_ledgers
|
||||
--
|
||||
-- REVISIT <AP>: 20090605
|
||||
-- We may not really need a ledgers table.
|
||||
-- It's not clear to me though, as we very
|
||||
-- possibly need to close out certain
|
||||
-- ledgers every so often, and just carry
|
||||
-- the balance forward (year end, etc).
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_ledgers`;
|
||||
CREATE TABLE `pmgr_ledgers` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
`name` VARCHAR(90) NOT NULL,
|
||||
|
||||
`account_id` INT(10) UNSIGNED NOT NULL,
|
||||
`sequence` INT(10) UNSIGNED DEFAULT 1,
|
||||
|
||||
`prior_ledger_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
`close_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
`close_transaction_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
@@ -948,20 +941,6 @@ CREATE TABLE `pmgr_ledgers` (
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_closes
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_closes`;
|
||||
CREATE TABLE `pmgr_closes` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_transactions
|
||||
@@ -970,17 +949,50 @@ 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',
|
||||
'RECEIPT',
|
||||
'DEPOSIT',
|
||||
'CLOSE',
|
||||
-- 'CREDIT',
|
||||
-- 'REFUND',
|
||||
-- 'WAIVER',
|
||||
'TRANSFER')
|
||||
NOT NULL,
|
||||
|
||||
`stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`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,
|
||||
-- All entries of a transaction should be for the same
|
||||
-- customer. By keeping track of customer here, it ensures
|
||||
-- that we can always track what's happening with the user
|
||||
-- even if there are only ledger entries, and no statement
|
||||
-- entries for some reason (the primary concern being the
|
||||
-- receipt of money, with nothing to pay for).
|
||||
-- customer_id can be NULL, for internal transfers and such.
|
||||
-- In that case, there should be no statement entries for
|
||||
-- this transaction, only ledger entries.
|
||||
-- REVISIT <AP>: 20090723
|
||||
-- It sounds like a transaction that has customer_id as NULL
|
||||
-- is really a fundamentally different type of "transaction".
|
||||
-- Do we need to have a new table for those type of
|
||||
-- entries / activities?
|
||||
`customer_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- The account/ledger of the transaction set
|
||||
-- (e.g. A/R, Bank, etc)
|
||||
`account_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
`ledger_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- For convenience. Actually, INVOICE will always set crdr
|
||||
-- to DEBIT, RECEIPT will use CREDIT, and DEPOSIT will use
|
||||
-- DEBIT
|
||||
`crdr` ENUM('DEBIT',
|
||||
'CREDIT')
|
||||
DEFAULT NULL,
|
||||
|
||||
-- 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,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
@@ -996,31 +1008,20 @@ 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,
|
||||
|
||||
-- The account/ledger of the entry
|
||||
`account_id` INT(10) UNSIGNED NOT NULL,
|
||||
`ledger_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- For convenience. Actually, CHARGE will always set crdr
|
||||
-- to CREDIT and PAYMENT will use DEBIT.
|
||||
`crdr` ENUM('DEBIT',
|
||||
'CREDIT')
|
||||
NOT NULL,
|
||||
|
||||
`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`)
|
||||
@@ -1029,15 +1030,24 @@ CREATE TABLE `pmgr_ledger_entries` (
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_reconciliations
|
||||
-- TABLE pmgr_double_entries
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_reconciliations`;
|
||||
CREATE TABLE `pmgr_reconciliations` (
|
||||
DROP TABLE IF EXISTS `pmgr_double_entries`;
|
||||
CREATE TABLE `pmgr_double_entries` (
|
||||
`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,
|
||||
-- The two entries that make up a "double entry"
|
||||
`debit_entry_id` INT(10) UNSIGNED NOT NULL,
|
||||
`credit_entry_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- REVISIT <AP>: 20090720
|
||||
-- The amount from ledger_entries should be moved here to
|
||||
-- eliminate duplication, and crdr should just be deleted.
|
||||
-- However, it can always be changed later, and I thinks
|
||||
-- those fields will come in handy when generating a
|
||||
-- a ledger report. So, duplication for now.
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
@@ -1045,14 +1055,137 @@ CREATE TABLE `pmgr_reconciliations` (
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_monetary_sources
|
||||
-- TABLE pmgr_statement_entries
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_monetary_sources`;
|
||||
CREATE TABLE `pmgr_monetary_sources` (
|
||||
DROP TABLE IF EXISTS `pmgr_statement_entries`;
|
||||
CREATE TABLE `pmgr_statement_entries` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
`type` ENUM('CHARGE',
|
||||
'PAYMENT',
|
||||
'SURPLUS',
|
||||
-- REVISIT <AP>: 20090730
|
||||
-- VOID is just to test out a theory
|
||||
-- for handling NSF and charge reversals
|
||||
'WAIVE',
|
||||
'VOID')
|
||||
NOT NULL,
|
||||
|
||||
`transaction_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- Effective date is when the charge/payment/transfer actually
|
||||
-- takes effect (since it may not be at the time of the transaction).
|
||||
-- Through date is used if/when a charge covers a certain time period,
|
||||
-- like rent. A security deposit, for example, would not use the
|
||||
-- through date.
|
||||
`effective_date` DATE DEFAULT NULL, -- first day
|
||||
`through_date` DATE DEFAULT NULL, -- last day
|
||||
`due_date` DATE DEFAULT NULL,
|
||||
|
||||
-- Customer ID is redundant, since it is saved as part of the
|
||||
-- transaction. Keeping it here anyway, for simplicity. If it's
|
||||
-- truly redundant, and unnecessary, we can always re
|
||||
`customer_id` INT(10) UNSIGNED NOT NULL,
|
||||
`lease_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
`amount` FLOAT(12,2) NOT NULL,
|
||||
|
||||
-- Account ID is used only for those statement entries that have
|
||||
-- a guaranteed single ledger entry, and thus single account.
|
||||
-- Right now, this is only used for charges.
|
||||
`account_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Allow the payment to reconcile against the charge
|
||||
`charge_entry_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_statement_entries_ledger_entries
|
||||
-- TABLE pmgr_statement_fractions
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_statement_fractions`;
|
||||
CREATE TABLE `pmgr_statement_fractions` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The two entries that make up a "double entry"
|
||||
`statement_entry_id` INT(10) UNSIGNED NOT NULL,
|
||||
`ledger_entry_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_tender_types
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_tender_types`;
|
||||
CREATE TABLE `pmgr_tender_types` (
|
||||
`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 legal tender name.
|
||||
`name` VARCHAR(80) NOT NULL,
|
||||
|
||||
-- Does this form of legal tender actually change hands?
|
||||
-- If so, then it's tillable. Examples include cash,
|
||||
-- checks, and money orders. Things that are not tillable
|
||||
-- include credit cards, debit cards, and ACH transfers.
|
||||
`tillable` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1,
|
||||
|
||||
-- Names of the 4 data fields (or NULL if not used)
|
||||
-- Not the most robust of solutions, especially since
|
||||
-- it requires (or strongly implicates) that all fields
|
||||
-- be of the same type (ugh). A more complete solution
|
||||
-- would be for each type to have its own table of data
|
||||
-- and to have that table specified here. However, this
|
||||
-- is MUCH simpler, and works for now.
|
||||
`data1_name` VARCHAR(80) DEFAULT NULL,
|
||||
`data2_name` VARCHAR(80) DEFAULT NULL,
|
||||
`data3_name` VARCHAR(80) DEFAULT NULL,
|
||||
`data4_name` VARCHAR(80) DEFAULT NULL,
|
||||
|
||||
-- When we accept legal tender of this form, where does
|
||||
-- it go? Each type of legal tender can specify an
|
||||
-- account, either distinct or non-distinct from others
|
||||
`account_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_tenders
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_tenders`;
|
||||
CREATE TABLE `pmgr_tenders` (
|
||||
`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 legal tender name.
|
||||
`name` VARCHAR(80) DEFAULT NULL,
|
||||
|
||||
-- The type of this legal tender
|
||||
`tender_type_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- The customer that provided this tender
|
||||
-- REVISIT <AP>: 20090728 Do we allow anonymous payments?
|
||||
`customer_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- REVISIT <AP>: 20090605
|
||||
-- Check Number;
|
||||
-- Routing Number, Account Number;
|
||||
@@ -1071,12 +1204,50 @@ CREATE TABLE `pmgr_monetary_sources` (
|
||||
`data3` VARCHAR(80) DEFAULT NULL,
|
||||
`data4` VARCHAR(80) DEFAULT NULL,
|
||||
|
||||
|
||||
-- The ledger entry this legal tender applies to
|
||||
`ledger_entry_id` INT(10) UNSIGNED NOT NULL,
|
||||
-- The ledger entry if this tender is marked NSF
|
||||
`nsf_ledger_entry_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
-- The deposit transaction that included these monies
|
||||
`deposit_transaction_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
-- The NSF transaction coming back from the bank.
|
||||
`nsf_transaction_id` INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- ----------------------------------------------------------------------
|
||||
-- ----------------------------------------------------------------------
|
||||
-- TABLE pmgr_deposits
|
||||
|
||||
DROP TABLE IF EXISTS `pmgr_deposits`;
|
||||
CREATE TABLE `pmgr_deposits` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
`transaction_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- The account/ledger of the entry
|
||||
`account_id` INT(10) UNSIGNED NOT NULL,
|
||||
`ledger_id` INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- For convenience. Should always be DEBIT (unless we
|
||||
-- decide to credit NSF instead of a negative debit).
|
||||
`crdr` ENUM('DEBIT')
|
||||
NOT NULL DEFAULT 'DEBIT',
|
||||
|
||||
`amount` FLOAT(12,2) NOT NULL,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
|
||||
|
||||
-- ######################################################################
|
||||
-- ######################################################################
|
||||
|
||||
@@ -37,7 +37,7 @@ Operations to be functional
|
||||
X - Create Customer ID/Account
|
||||
X - Add Contact information to Customer
|
||||
X - Move Customer into Unit
|
||||
X - Enter Rent Concessions given
|
||||
? - Enter Rent Concessions given
|
||||
X - Asses Rent Charges
|
||||
X - Asses Late Charges
|
||||
X - Asses Security Deposits
|
||||
@@ -45,7 +45,7 @@ Operations to be functional
|
||||
X - Receive and record Money Orders
|
||||
X - Receive and record Cash
|
||||
X - Receive and record ACH Deposits
|
||||
x - Reverse rent charges (early moveout on prepaid occupancy)
|
||||
? - Reverse rent charges (early moveout on prepaid occupancy)
|
||||
X - Handle NSF checks
|
||||
X - Assess NSF Fees
|
||||
X - Determine Lease Paid-Through status
|
||||
@@ -55,15 +55,14 @@ Operations to be functional
|
||||
- Flag unit as normal status
|
||||
- Flag unit as dirty
|
||||
- Enter notes when communicating with Customer
|
||||
- Accept pre-payments
|
||||
- NOTE: As a temporary solution, disallow customer to run
|
||||
a credit. Require charges be entered first.
|
||||
X - Accept pre-payments
|
||||
X - Record Customer Move-Out from Unit
|
||||
X - Record utilization of Security Deposit
|
||||
? - Record utilization of Security Deposit
|
||||
- Record issuing of a refund
|
||||
- Record Deposit into Petty Cash
|
||||
- Record Payment from Petty Cash to expenses
|
||||
- Record Petty Cash to refund.
|
||||
X - Write Off Bad Debt
|
||||
X - Perform a Deposit and Close the Books
|
||||
? - Write Off Bad Debt
|
||||
X - Perform a Deposit
|
||||
X - Close the Books (nightly / weekly, etc)
|
||||
X - Determine Rents Collected for a given period.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,103 +84,83 @@ class AppController extends Controller {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* helper: jqGridView
|
||||
* - called by function to create an index listing
|
||||
* helper: gridView
|
||||
* - called by derived controllers to create an index listing
|
||||
*/
|
||||
|
||||
function jqGridView($title, $action = null, $element = null) {
|
||||
function gridView($title, $action = null, $element = null) {
|
||||
$this->set('title', $title);
|
||||
// The resulting page will contain a jqGrid, which will
|
||||
// The resulting page will contain a grid, which will
|
||||
// use ajax to obtain the actual data for this action
|
||||
$this->set('action', $action ? $action : $this->params['action']);
|
||||
$this->render('/elements/' . ($element ? $element : $this->params['controller']));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: jqGridData
|
||||
* - Fetches the actual data requested by jqGrid as XML
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: gridData
|
||||
* - Fetches the actual data requested by grid as XML
|
||||
*/
|
||||
|
||||
function jqGridData() {
|
||||
function gridData() {
|
||||
// Grab a copy of the parameters that control this request
|
||||
$params = array();
|
||||
if (isset($this->params['url']) && is_array($this->params['url']))
|
||||
$params = $this->params['url'];
|
||||
|
||||
// Do any preliminary setup necessary
|
||||
$this->jqGridDataSetup($params);
|
||||
$this->gridDataSetup($params);
|
||||
|
||||
// Get the top level model for this grid
|
||||
$model = $this->jqGridDataModel($params);
|
||||
|
||||
// Establish the basic query and conditions
|
||||
$query = array_intersect_key($this->jqGridDataCountTables($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
$query['conditions'] = $this->jqGridDataCountConditions($params, $model);
|
||||
$query['group'] = $this->jqGridDataCountGroup($params, $model);
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
$params['count_query'] = $query;
|
||||
$model = $this->gridDataModel($params);
|
||||
|
||||
// Get the number of records prior to pagination
|
||||
$count = $this->jqGridDataRecordCount($params, $model, $query);
|
||||
$count = $this->gridDataCount($params, $model);
|
||||
|
||||
// Start the query over, this time getting the actual set
|
||||
// of tables needed, not just those needed for counting.
|
||||
$query = array_intersect_key($this->jqGridDataTables($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
$query['conditions'] = $this->jqGridDataConditions($params, $model);
|
||||
// Determine pagination configuration (and save to $params)
|
||||
$pagination = $this->gridDataPagination($params, $model, $count);
|
||||
|
||||
// Verify a few parameters and determine our starting row
|
||||
$limit = $params['rows'];
|
||||
$total = ($count < 0) ? 0 : ceil($count/$limit);
|
||||
$page = ($params['page'] <= 1) ? 1 : (($params['page'] > $total) ? $total : $params['page']);
|
||||
$start = $limit*$page - $limit;
|
||||
|
||||
// Grab the actual records taking pagination into account
|
||||
$query['group'] = $this->jqGridDataGroup($params, $model);
|
||||
$query['order'] = $this->jqGridDataOrder($params, $model,
|
||||
isset($params['sidx']) ? $params['sidx'] : null,
|
||||
isset($params['sord']) ? $params['sord'] : null);
|
||||
$query['limit'] = $this->jqGridDataLimit($params, $model, $start, $limit);
|
||||
$query['fields'] = $this->jqGridDataFields($params, $model);
|
||||
$results = $this->jqGridDataRecords($params, $model, $query);
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
$params['query'] = $query;
|
||||
// Retreive the appropriate subset of data
|
||||
$records = $this->gridDataRecords($params, $model, $pagination);
|
||||
|
||||
// Post process the records
|
||||
$this->jqGridRecordsPostProcess($params, $model, $results);
|
||||
$this->gridDataPostProcess($params, $model, $records);
|
||||
|
||||
// Add in any needed hyperlinks
|
||||
$this->jqGridRecordLinks($params, $model, $results, array());
|
||||
|
||||
// Finally, dump out the data
|
||||
$this->jqGridDataOutputHeader($params, $model);
|
||||
echo "<?xml version='1.0' encoding='utf-8'?>\n";
|
||||
echo "<rows>\n";
|
||||
$this->jqGridDataOutputSummary($params, $model, $page, $total, $count);
|
||||
$this->jqGridDataOutputRecords($params, $model, $results);
|
||||
echo "</rows>\n";
|
||||
// Output the resulting record set
|
||||
$this->gridDataOutput($params, $model, $records, $pagination);
|
||||
|
||||
// Call out to finalize everything
|
||||
$this->jqGridDataFinalize($params);
|
||||
$this->gridDataFinalize($params);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtual: jqGridData* functions
|
||||
* - These set up the context for the jqGrid data, and will
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtual: gridData* functions
|
||||
* - These set up the context for the grid data, and will
|
||||
* need to be overridden in the derived class for anything
|
||||
* other than the most basic of grids.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData SETUP / CLEANUP
|
||||
*/
|
||||
|
||||
function gridDataModel(&$params) {
|
||||
return $this->{$this->modelClass};
|
||||
}
|
||||
|
||||
function gridDataSetup(&$params) {
|
||||
// Assume we're debugging.
|
||||
// The actual jqGrid request will set this to false
|
||||
// The actual grid request will set this to false
|
||||
$debug = true;
|
||||
|
||||
if (isset($this->passedArgs['debug']))
|
||||
@@ -188,7 +168,10 @@ class AppController extends Controller {
|
||||
|
||||
$params['debug'] = $debug;
|
||||
|
||||
if (!$debug) {
|
||||
if ($debug) {
|
||||
ob_start();
|
||||
}
|
||||
else {
|
||||
$this->layout = null;
|
||||
$this->autoLayout = false;
|
||||
$this->autoRender = false;
|
||||
@@ -200,46 +183,350 @@ class AppController extends Controller {
|
||||
'rows' => 20),
|
||||
$params);
|
||||
|
||||
// Unserialize our complex structure of fields
|
||||
// Unserialize our complex structure of post data.
|
||||
// This SHOULD always be set, except when debugging
|
||||
if (isset($params['fields']))
|
||||
$params['fields'] = unserialize($params['fields']);
|
||||
else
|
||||
$params['fields'] = array($this->{$this->modelClass}->alias
|
||||
.'.'.
|
||||
$this->{$this->modelClass}->primarKey);
|
||||
if (isset($params['post']))
|
||||
$params['post'] = unserialize($params['post']);
|
||||
|
||||
// Unserialize the list of ids, if present.
|
||||
if (isset($params['custom']))
|
||||
$params['custom'] = unserialize($params['custom']);
|
||||
// Unserialize our complex structure of dynamic post data
|
||||
if (isset($params['dynamic_post']))
|
||||
$params['dynamic_post'] = unserialize($params['dynamic_post']);
|
||||
|
||||
// Merge the static and dynamic post data
|
||||
if (empty($params['post']) && !empty($params['dynamic_post']))
|
||||
$params['post'] = $params['dynamic_post'];
|
||||
elseif (!empty($params['post']) && !empty($params['dynamic_post']))
|
||||
//$params['post'] = array_merge($params['post'], $params['dynamic_post']);
|
||||
$params['post'] = array_merge_recursive($params['post'], $params['dynamic_post']);
|
||||
|
||||
// This SHOULD always be set, except when debugging
|
||||
if (!isset($params['post']['fields']))
|
||||
$params['post']['fields'] = array($this->{$this->modelClass}->alias
|
||||
.'.'.
|
||||
$this->{$this->modelClass}->primaryKey);
|
||||
|
||||
// Make sure the action parameter at least exists, and
|
||||
// promote it to the top level (since it drives the operation).
|
||||
if (isset($params['post']['action']))
|
||||
$params['action'] = $params['post']['action'];
|
||||
else
|
||||
$params['custom'] = null;
|
||||
|
||||
// Unserialize the list of ids, if present.
|
||||
if (isset($params['idlist']))
|
||||
$params['idlist'] = unserialize($params['idlist']);
|
||||
|
||||
$params['action'] = null;
|
||||
}
|
||||
|
||||
function jqGridDataModel(&$params) {
|
||||
return $this->{$this->modelClass};
|
||||
function gridDataFinalize(&$params) {
|
||||
if ($params['debug']) {
|
||||
$xml = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$xml = preg_replace("/&/", "&", $xml);
|
||||
$xml = preg_replace("/</", "<", $xml);
|
||||
$xml = preg_replace("/>/", ">", $xml);
|
||||
echo ("\n<PRE>\n$xml\n</PRE>\n");
|
||||
}
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
// If not overridden, we use the same tables to
|
||||
// perform our count as we do to for the actual query
|
||||
return $this->jqGridDataTables($params, $model);
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData COUNTING
|
||||
*/
|
||||
|
||||
function gridDataCount(&$params, &$model) {
|
||||
// Establish the tables and conditions for counting
|
||||
$query = array_intersect_key($this->gridDataCountTableSet($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
|
||||
// Conditions for the count
|
||||
$query['conditions'] = $this->gridDataCountConditionSet($params, $model);
|
||||
|
||||
// Grouping (which would not be typical)
|
||||
$query['group'] = $this->gridDataCountGroup($params, $model);
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
$params['count_query'] = $query;
|
||||
|
||||
// Get the number of records prior to pagination
|
||||
return $this->gridDataCountExecute($params, $model, $query);
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
return array('contain' => false);
|
||||
function gridDataCountExecute(&$params, &$model, $query) {
|
||||
return $model->find('count', $query);
|
||||
}
|
||||
|
||||
function jqGridDataCountConditions(&$params, &$model) {
|
||||
return $this->jqGridDataConditions($params, $model);
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
// Same tables for counting as for retreiving
|
||||
return $this->gridDataTables($params, $model);
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
function gridDataCountTableSet(&$params, &$model) {
|
||||
// Preliminary set of tables
|
||||
$query = array_intersect_key($this->gridDataCountTables($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
|
||||
// Perform filtering based on user request: $params['post']['filter']
|
||||
return array_intersect_key($this->gridDataFilterTables($params, $model, $query),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
}
|
||||
|
||||
function gridDataCountConditions(&$params, &$model) {
|
||||
// Same conditions for counting as for retreiving
|
||||
return $this->gridDataConditions($params, $model);
|
||||
}
|
||||
|
||||
function gridDataCountConditionSet(&$params, &$model) {
|
||||
// Conditions for the count
|
||||
$conditions = $this->gridDataCountConditions($params, $model);
|
||||
|
||||
// Perform filtering based on user request: $params['post']['filter']
|
||||
return $this->gridDataFilterConditions($params, $model, $conditions);
|
||||
}
|
||||
|
||||
function gridDataCountGroup(&$params, &$model) {
|
||||
// Grouping will screw up the count, since it
|
||||
// causes the results to be split into chunks
|
||||
// based on the GROUP BY clause. We can't have
|
||||
// more than one row of data in the count query,
|
||||
// just a _single_ row with the actual count.
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData FILTERING
|
||||
*/
|
||||
|
||||
function gridDataFilterTables(&$params, &$model, $query) {
|
||||
if (isset($query['contain']))
|
||||
$link = 'contain';
|
||||
else
|
||||
$link = 'link';
|
||||
|
||||
if (empty($params['post']['filter']))
|
||||
return $query;
|
||||
|
||||
foreach ($params['post']['filter'] AS $filter => $filter_value) {
|
||||
$split = $this->gridDataFilterSplit($params, $model, $filter, $filter_value);
|
||||
|
||||
/* pr(array('AppController::gridDataFilterTable' => */
|
||||
/* array('checkpoint' => "Filter split") */
|
||||
/* + compact('split'))); */
|
||||
|
||||
$table = $this->gridDataFilterTablesTable($params, $model, $split['table']);
|
||||
if (!$table)
|
||||
continue;
|
||||
|
||||
$config = $this->gridDataFilterTablesConfig($params, $model, $split['table']);
|
||||
|
||||
/* pr(array('AppController::gridDataFilterTable' => */
|
||||
/* array('checkpoint' => "Add filter config to query") */
|
||||
/* + array('query[link]' => $query[$link]) */
|
||||
/* + compact('config'))); */
|
||||
|
||||
// If the table is already part of the query, append to it
|
||||
if ($table == $model->alias) {
|
||||
$query[$link] =
|
||||
array_merge_recursive(array_diff_key($config, array('fields'=>1)),
|
||||
$query[$link]);
|
||||
}
|
||||
elseif (isset($query[$link][$table])) {
|
||||
$query[$link][$table] =
|
||||
array_merge_recursive($config, $query[$link][$table]);
|
||||
}
|
||||
elseif (in_array($table, $query[$link])) {
|
||||
// REVISIT <AP>: 20090727
|
||||
// This presents a dilema. $config may specify fields
|
||||
// as array(), whereas the user has already indicated
|
||||
// they desire ALL fields (by the fact that table is
|
||||
// a member of the query['link'] array without anything
|
||||
// specified). However, $config _could_ specify a
|
||||
// non-standard field in the array, such as using SUM()
|
||||
// or other equation. In that case, we'd have to add
|
||||
// in all the fields of table to the array. That
|
||||
// wouldn't be very hard, but I'll ignore it at the
|
||||
// moment and wait until it's needed.
|
||||
$key = array_search($table, $query[$link]);
|
||||
$query[$link][$table] = array('fields' => null) + $config;
|
||||
unset($query[$link][$key]);
|
||||
}
|
||||
else {
|
||||
// Table is NOT already part of the query... set it now
|
||||
$query[$link][$table] = $config;
|
||||
}
|
||||
|
||||
/* pr(array('post-filter-table-config' => */
|
||||
/* array('query[link]' => $query[$link]))); */
|
||||
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
function gridDataFilterTablesTable(&$params, &$model, $table) {
|
||||
// By default, don't add anything if the filter
|
||||
// condition occurs on the actual model table
|
||||
if ($table == $model->alias)
|
||||
return null;
|
||||
return $this->gridDataFilterTableName($params, $model, $table);
|
||||
}
|
||||
|
||||
function gridDataFilterTablesConfig(&$params, &$model, $table) {
|
||||
return array('fields' => array());
|
||||
}
|
||||
|
||||
function gridDataFilterConditions(&$params, &$model, $conditions) {
|
||||
if (empty($params['post']['filter']))
|
||||
return $conditions;
|
||||
|
||||
foreach ($params['post']['filter'] AS $filter => $filter_value) {
|
||||
$split = $this->gridDataFilterSplit($params, $model, $filter, $filter_value);
|
||||
|
||||
$table = $this->gridDataFilterConditionsTable($params, $model, $split['table']);
|
||||
$key = $this->gridDataFilterConditionsKey($params, $model, $split['table'], $split['field']);
|
||||
if (!$key)
|
||||
continue;
|
||||
|
||||
$conditions[]
|
||||
= $this->gridDataFilterConditionsStatement($params, $model, $table, $key,
|
||||
array_intersect_key
|
||||
($split,
|
||||
array('value'=>1,
|
||||
'value_present'=>1)));
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function gridDataFilterConditionsTable(&$params, &$model, $table) {
|
||||
return $this->gridDataFilterTableName($params, $model, $table);
|
||||
}
|
||||
|
||||
function gridDataFilterConditionsKey(&$params, &$model, $table, $id) {
|
||||
// REVISIT <AP>: 20090722
|
||||
// When $id is null, we could instantiate the table,
|
||||
// and use the _actual_ primary key. However, I don't
|
||||
// expect that functionality to be used, and will just
|
||||
// stick with 'id' for now.
|
||||
return $id ? $id : 'id';
|
||||
}
|
||||
|
||||
function gridDataFilterConditionsStatement(&$params, &$model, $table, $key, $value) {
|
||||
$key = (empty($table)?"":"{$table}.") . $key;
|
||||
if (isset($value['value_present']) && $value['value_present'])
|
||||
return array($key => $value['value']);
|
||||
else
|
||||
return array($key);
|
||||
}
|
||||
|
||||
function gridDataFilterSplit(&$params, &$model, $filter, $value) {
|
||||
$value_present = true;
|
||||
if (!is_string($filter)) {
|
||||
// only a filter condition was set, not filter=>value
|
||||
$filter = $value;
|
||||
$value = null;
|
||||
$value_present = false;
|
||||
}
|
||||
|
||||
$table = $field = null;
|
||||
if (preg_match("/^\w+\.\w+(\s*[!<>=]+)?$/", $filter)) {
|
||||
list($table, $field) = explode(".", $filter);
|
||||
}
|
||||
elseif (preg_match('/^[A-Z][A-Za-z]*$/', $filter)) {
|
||||
$table = $filter;
|
||||
$field = null;
|
||||
}
|
||||
elseif (preg_match('/^\w+(\s*[!<>=]+)?$/', $filter)) {
|
||||
$table = $model->alias;
|
||||
$field = $filter;
|
||||
}
|
||||
else {
|
||||
// $filter must be a function or other complicated condition
|
||||
$table = null;
|
||||
$field = $filter;
|
||||
}
|
||||
|
||||
return compact('table', 'field', 'value', 'value_present');
|
||||
}
|
||||
|
||||
function gridDataFilterTableName(&$params, &$model, $table) {
|
||||
return Inflector::camelize($table);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData PAGINATION
|
||||
*/
|
||||
|
||||
function gridDataPagination(&$params, &$model, $record_count) {
|
||||
// Retrieve, or calculate, all parameters necesssary for
|
||||
// pagination. Verify the passed in parameters are valid.
|
||||
|
||||
$limit = $params['rows'] > 0 ? $params['rows'] : 10;
|
||||
$total = ($record_count < 0) ? 0 : ceil($record_count/$limit);
|
||||
$page = ($params['page'] <= 1) ? 1 : (($params['page'] > $total) ? $total : $params['page']);
|
||||
$start = $limit * ($page - 1);
|
||||
|
||||
return compact('record_count', 'limit', 'page', 'start', 'total');
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData RETREIVAL
|
||||
*/
|
||||
|
||||
function gridDataRecords(&$params, &$model, $pagination) {
|
||||
// Establish the tables for this query
|
||||
$query = array_intersect_key($this->gridDataTableSet($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
|
||||
// Specify the fields for the query
|
||||
$query['fields'] = $this->gridDataFields($params, $model);
|
||||
|
||||
// Conditions of the query
|
||||
$query['conditions'] = $this->gridDataConditionSet($params, $model);
|
||||
|
||||
// Data record grouping
|
||||
$query['group'] = $this->gridDataGroup($params, $model);
|
||||
|
||||
// The subset of data based on pagination
|
||||
$query['limit'] = $this->gridDataLimit($params, $model, $pagination['start'], $pagination['limit']);
|
||||
|
||||
// Ordering based on user request
|
||||
$query['order'] = $this->gridDataOrder($params, $model,
|
||||
isset($params['sidx']) ? $params['sidx'] : null,
|
||||
isset($params['sord']) ? $params['sord'] : null);
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
$params['query'] = $query;
|
||||
|
||||
return $this->gridDataRecordsExecute($params, $model, $query);
|
||||
}
|
||||
|
||||
function gridDataRecordsExecute(&$params, &$model, $query) {
|
||||
return $model->find('all', $query);
|
||||
}
|
||||
|
||||
function gridDataTables(&$params, &$model) {
|
||||
return array('link' => array());
|
||||
}
|
||||
|
||||
function gridDataTableSet(&$params, &$model) {
|
||||
// Preliminary set of tables
|
||||
$query = array_intersect_key($this->gridDataTables($params, $model),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
|
||||
// Perform filtering based on user request: $params['post']['filter']
|
||||
$query = array_intersect_key($this->gridDataFilterTables($params, $model, $query),
|
||||
array('link'=>1, 'contain'=>1));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$searches = array();
|
||||
|
||||
if (isset($params['_search']) && $params['_search'] === 'true') {
|
||||
@@ -281,8 +568,8 @@ class AppController extends Controller {
|
||||
}
|
||||
|
||||
if (isset($params['action']) && $params['action'] === 'idlist') {
|
||||
if (count($params['idlist']))
|
||||
$conditions[] = array($model->alias.'.'.$model->primaryKey => $params['idlist']);
|
||||
if (count($params['post']['idlist']))
|
||||
$conditions[] = array($model->alias.'.'.$model->primaryKey => $params['post']['idlist']);
|
||||
else
|
||||
$conditions[] = '0=1';
|
||||
}
|
||||
@@ -290,40 +577,85 @@ class AppController extends Controller {
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridDataFields(&$params, &$model) {
|
||||
return null;
|
||||
function gridDataConditionSet(&$params, &$model) {
|
||||
// Conditions for record retrieval
|
||||
$conditions = $this->gridDataConditions($params, $model);
|
||||
|
||||
// Perform filtering based on user request: $params['post']['filter']
|
||||
return $this->gridDataFilterConditions($params, $model, $conditions);
|
||||
}
|
||||
|
||||
function jqGridDataCountGroup(&$params, &$model) {
|
||||
return null;
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$db = &$model->getDataSource();
|
||||
$fields = $db->fields($model, $model->alias);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
function jqGridDataGroup(&$params, &$model) {
|
||||
function gridDataGroup(&$params, &$model) {
|
||||
return $model->alias.'.'.$model->primaryKey;
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
return $index ? array($index .' '. $direction) : null;
|
||||
}
|
||||
|
||||
function jqGridDataLimit(&$params, &$model, $start, $limit) {
|
||||
function gridDataLimit(&$params, &$model, $start, $limit) {
|
||||
return $start . ', ' . $limit;
|
||||
}
|
||||
|
||||
function jqGridDataRecordCount(&$params, &$model, $query) {
|
||||
return $model->find('count', $query);
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData POST PROCESSING
|
||||
*/
|
||||
|
||||
function gridDataPostProcess(&$params, &$model, &$records) {
|
||||
// Add grid IDs to each record
|
||||
$this->gridDataPostProcessGridIDs($params, $model, $records);
|
||||
|
||||
// Add the calculated fields (if any), to the model fields
|
||||
$this->gridDataPostProcessCalculatedFields($params, $model, $records);
|
||||
|
||||
// Perform any requested subtotaling of fields
|
||||
$this->gridDataPostProcessSubtotal($params, $model, $records);
|
||||
|
||||
// Add in any needed hyperlinks
|
||||
$this->gridDataPostProcessLinks($params, $model, $records, array());
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
//$params['records'] = $records;
|
||||
}
|
||||
|
||||
function jqGridDataRecords(&$params, &$model, $query) {
|
||||
return $model->find('all', $query);
|
||||
}
|
||||
|
||||
function jqGridRecordsPostProcess(&$params, &$model, &$records) {
|
||||
function gridDataPostProcessGridIDs(&$params, &$model, &$records) {
|
||||
$model_alias = $model->alias;
|
||||
$id = $model->primaryKey;
|
||||
|
||||
foreach ($records AS &$record) {
|
||||
$record['grid_id'] = $record[$model_alias][$id];
|
||||
}
|
||||
}
|
||||
|
||||
function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) {
|
||||
$model_alias = $model->alias;
|
||||
|
||||
foreach ($records AS &$record) {
|
||||
// Add the calculated fields (if any), to the model fields
|
||||
if (isset($record[0])) {
|
||||
$record[$model_alias] += $record[0];
|
||||
unset($record[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function gridDataPostProcessSubtotal(&$params, &$model, &$records) {
|
||||
$model_alias = $model->alias;
|
||||
|
||||
// REVISIT <AP>: 20090722
|
||||
// Horrible solution to something that should be done
|
||||
// in SQL. But, it works for now, so what the heck...
|
||||
|
||||
$subtotals = array();
|
||||
foreach ($params['fields'] AS $field) {
|
||||
foreach ($params['post']['fields'] AS $field) {
|
||||
if (preg_match('/subtotal-(.*)$/', $field, $matches))
|
||||
$subtotals[] = array('field' => $matches[1],
|
||||
'name' => $field,
|
||||
@@ -331,13 +663,6 @@ class AppController extends Controller {
|
||||
}
|
||||
|
||||
foreach ($records AS &$record) {
|
||||
$record['jqGrid_id'] = $record[$model_alias][$id];
|
||||
// Add the calculated fields (if any), to the model fields
|
||||
if (isset($record[0])) {
|
||||
$record[$model_alias] += $record[0];
|
||||
unset($record[0]);
|
||||
}
|
||||
|
||||
foreach ($subtotals AS &$subtotal) {
|
||||
$field = $subtotal['field'];
|
||||
if (preg_match("/\./", $field)) {
|
||||
@@ -351,12 +676,9 @@ class AppController extends Controller {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG PURPOSES ONLY!
|
||||
//$params['records'] = $records;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
// Don't create any links if ordered not to.
|
||||
if (isset($params['nolinks']))
|
||||
return;
|
||||
@@ -389,20 +711,44 @@ class AppController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
function jqGridDataOutputHeader(&$params, &$model) {
|
||||
if ($params['debug']) {
|
||||
ob_start();
|
||||
}
|
||||
else {
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
* gridData OUTPUT
|
||||
*/
|
||||
|
||||
function gridDataOutput(&$params, &$model, &$records, $pagination) {
|
||||
$this->gridDataOutputHeader($params, $model);
|
||||
$this->gridDataOutputXMLHeader($params, $model);
|
||||
$this->gridDataOutputRootNodeBegin($params, $model);
|
||||
$this->gridDataOutputSummary($params, $model, $pagination);
|
||||
$this->gridDataOutputRecords($params, $model, $records);
|
||||
$this->gridDataOutputRootNodeEnd($params, $model);
|
||||
}
|
||||
|
||||
function gridDataOutputHeader(&$params, &$model) {
|
||||
if (!$params['debug']) {
|
||||
header("Content-type: text/xml;charset=utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
function jqGridDataOutputSummary(&$params, &$model, $page, $total, $records) {
|
||||
function gridDataOutputXMLHeader(&$params, &$model) {
|
||||
echo "<?xml version='1.0' encoding='utf-8'?>\n";
|
||||
}
|
||||
|
||||
function gridDataOutputRootNodeBegin(&$params, &$model) {
|
||||
echo "<rows>\n";
|
||||
}
|
||||
|
||||
function gridDataOutputRootNodeEnd(&$params, &$model) {
|
||||
echo "</rows>\n";
|
||||
}
|
||||
|
||||
function gridDataOutputSummary(&$params, &$model, $pagination) {
|
||||
echo " <params><![CDATA[\n" . print_r($params, true) . "\n]]></params>\n";
|
||||
echo " <page>$page</page>\n";
|
||||
echo " <total>$total</total>\n";
|
||||
echo " <records>$records</records>\n";
|
||||
echo " <page>{$pagination['page']}</page>\n";
|
||||
echo " <total>{$pagination['total']}</total>\n";
|
||||
echo " <records>{$pagination['record_count']}</records>\n";
|
||||
|
||||
if (isset($params['userdata'])) {
|
||||
foreach ($params['userdata'] AS $field => $value)
|
||||
@@ -410,34 +756,35 @@ class AppController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
function jqGridDataOutputRecords(&$params, &$model, &$records) {
|
||||
$id_field = 'jqGrid_id';
|
||||
function gridDataOutputRecords(&$params, &$model, &$records) {
|
||||
$id_field = 'grid_id';
|
||||
foreach ($records AS $record) {
|
||||
$this->jqGridDataOutputRecord($params, $model, $record,
|
||||
$record[$id_field], $params['fields']);
|
||||
$this->gridDataOutputRecord($params, $model, $record,
|
||||
$record[$id_field], $params['post']['fields']);
|
||||
}
|
||||
}
|
||||
|
||||
function jqGridDataOutputRecord(&$params, &$model, &$record, $id, $fields) {
|
||||
function gridDataOutputRecord(&$params, &$model, &$record, $id, $fields) {
|
||||
echo " <row id='$id'>\n";
|
||||
foreach ($fields AS $field) {
|
||||
$this->jqGridDataOutputRecordField($params, $model, $record, $field);
|
||||
$this->gridDataOutputRecordField($params, $model, $record, $field);
|
||||
}
|
||||
echo " </row>\n";
|
||||
}
|
||||
|
||||
function jqGridDataOutputRecordField(&$params, &$model, &$record, $field) {
|
||||
function gridDataOutputRecordField(&$params, &$model, &$record, $field) {
|
||||
if (preg_match("/\./", $field)) {
|
||||
list($tbl, $col) = explode(".", $field);
|
||||
//pr(compact('record', 'field', 'tbl', 'col'));
|
||||
$data = $record[$tbl][$col];
|
||||
}
|
||||
else {
|
||||
$data = $record[$model->alias][$field];
|
||||
}
|
||||
$this->jqGridDataOutputRecordCell($params, $model, $record, $field, $data);
|
||||
$this->gridDataOutputRecordCell($params, $model, $record, $field, $data);
|
||||
}
|
||||
|
||||
function jqGridDataOutputRecordCell(&$params, &$model, &$record, $field, $data) {
|
||||
function gridDataOutputRecordCell(&$params, &$model, &$record, $field, $data) {
|
||||
// be sure to put text data in CDATA
|
||||
if (preg_match("/^\d*$/", $data))
|
||||
echo " <cell>$data</cell>\n";
|
||||
@@ -445,17 +792,5 @@ class AppController extends Controller {
|
||||
echo " <cell><![CDATA[$data]]></cell>\n";
|
||||
}
|
||||
|
||||
function jqGridDataFinalize(&$params) {
|
||||
if ($params['debug']) {
|
||||
$xml = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$xml = preg_replace("/&/", "&", $xml);
|
||||
$xml = preg_replace("/</", "<", $xml);
|
||||
$xml = preg_replace("/>/", ">", $xml);
|
||||
|
||||
echo ("\n<PRE>\n$xml\n</PRE>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -42,6 +42,146 @@ class AppModel extends Model {
|
||||
var $useNullForEmpty = true;
|
||||
var $formatDateFields = true;
|
||||
|
||||
// Default Log Level, if not specified at the function level
|
||||
var $default_log_level = 5;
|
||||
|
||||
// Class specific log levels
|
||||
var $class_log_level = array('Model' => 5);
|
||||
|
||||
// Function specific log levels
|
||||
var $function_log_level = array();
|
||||
|
||||
// Force the module to log at LEAST at this level
|
||||
var $min_log_level;
|
||||
|
||||
// Force logging of nothing higher than this level
|
||||
var $max_log_level;
|
||||
|
||||
|
||||
// REVISIT <AP>: 20090730
|
||||
// Why is this constructor crashing?
|
||||
// Clearly it's in some sort of infinite
|
||||
// loop, but it seems the correct way
|
||||
// to have a constructor call the parent...
|
||||
|
||||
/* function __construct() { */
|
||||
/* parent::__construct(); */
|
||||
/* $this->prClassLevel(5, 'Model'); */
|
||||
/* } */
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: pr
|
||||
* - Prints out debug information, if the log level allows
|
||||
*/
|
||||
|
||||
function prClassLevel($level, $class = null) {
|
||||
$trace = debug_backtrace(false);
|
||||
$caller = array_shift($trace);
|
||||
$caller = array_shift($trace);
|
||||
if (empty($class))
|
||||
$class = $caller['class'];
|
||||
$this->class_log_level[$class] = $level;
|
||||
}
|
||||
|
||||
function prFunctionLevel($level, $function = null, $class = null) {
|
||||
$trace = debug_backtrace(false);
|
||||
$caller = array_shift($trace);
|
||||
$caller = array_shift($trace);
|
||||
if (empty($class))
|
||||
$class = $caller['class'];
|
||||
if (empty($function))
|
||||
$function = $caller['function'];
|
||||
$this->function_log_level["{$class}-{$function}"] = $level;
|
||||
}
|
||||
|
||||
function _pr($level, $mixed, $checkpoint = null) {
|
||||
if (Configure::read() <= 0)
|
||||
return;
|
||||
|
||||
$log_level = $this->default_log_level;
|
||||
|
||||
$trace = debug_backtrace(false);
|
||||
|
||||
// Get rid of pr/prEnter/prReturn
|
||||
$caller = array_shift($trace);
|
||||
|
||||
// The next entry shows where pr was called from, but it
|
||||
// shows _what_ was called, which is pr/prEntry/prReturn.
|
||||
$caller = array_shift($trace);
|
||||
$file = $caller['file'];
|
||||
$line = $caller['line'];
|
||||
|
||||
// So, this caller holds the calling function name
|
||||
$caller = array_shift($trace);
|
||||
$function = $caller['function'];
|
||||
$class = $caller['class'];
|
||||
//$class = $this->name;
|
||||
|
||||
// Adjust the log level from default, if necessary
|
||||
if (isset($this->class_log_level[$class]))
|
||||
$log_level = $this->class_log_level[$class];
|
||||
if (isset($this->function_log_level["{$class}-{$function}"]))
|
||||
$log_level = $this->function_log_level["{$class}-{$function}"];
|
||||
if (isset($this->min_log_level))
|
||||
$log_level = max($log_level, $this->min_log_level);
|
||||
if (isset($this->max_log_level))
|
||||
$log_level = min($log_level, $this->max_log_level);
|
||||
|
||||
// If the level is insufficient, bail out
|
||||
if ($level > $log_level)
|
||||
return;
|
||||
|
||||
if (!empty($checkpoint)) {
|
||||
$chk = array("checkpoint" => $checkpoint);
|
||||
if (is_array($mixed))
|
||||
$mixed = $chk + $mixed;
|
||||
else
|
||||
$mixed = $chk + array($mixed);
|
||||
}
|
||||
|
||||
echo '<DIV CLASS="pr-caller">';
|
||||
echo '<strong>' . substr(str_replace(ROOT, '', $file), 1) . '</strong>';
|
||||
echo ' (line <strong>' . $line . '</strong>)';
|
||||
echo ' : level-' . $level;
|
||||
echo '</DIV>' . "\n";
|
||||
|
||||
pr(array("{$class}::{$function}()" => $mixed), false, false);
|
||||
}
|
||||
|
||||
function pr($level, $mixed, $checkpoint = null) {
|
||||
$this->_pr($level, $mixed, $checkpoint);
|
||||
}
|
||||
|
||||
function prEnter($args, $level = 15) {
|
||||
$this->_pr($level, $args, 'Function Entry');
|
||||
}
|
||||
|
||||
function prReturn($retval, $level = 16) {
|
||||
$this->_pr($level, $retval, 'Function Return');
|
||||
return $retval;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: queryInit
|
||||
* - Initializes the query fields
|
||||
*/
|
||||
function prDump($all = false) {
|
||||
$vars = get_object_vars($this);
|
||||
foreach (array_keys($vars) AS $name) {
|
||||
if (preg_match("/^[A-Z]/", $name))
|
||||
unset($vars[$name]);
|
||||
if (preg_match("/^_/", $name) && !$all)
|
||||
unset($vars[$name]);
|
||||
}
|
||||
pr($vars);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Enum Values
|
||||
* Snippet v0.1.3
|
||||
@@ -81,7 +221,31 @@ class AppModel extends Model {
|
||||
} //end getEnumValues
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: queryInit
|
||||
* - Initializes the query fields
|
||||
*/
|
||||
function queryInit(&$query, $link = true) {
|
||||
if (!isset($query))
|
||||
$query = array();
|
||||
if (!isset($query['conditions']))
|
||||
$query['conditions'] = array();
|
||||
if (!isset($query['group']))
|
||||
$query['group'] = null;
|
||||
if (!isset($query['fields']))
|
||||
$query['fields'] = null;
|
||||
if ($link && !isset($query['link']))
|
||||
$query['link'] = array();
|
||||
if (!$link && !isset($query['contain']))
|
||||
$query['contain'] = array();
|
||||
|
||||
// In case caller expects query to come back
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
*
|
||||
* $uninflectedPlural = array('.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox');
|
||||
*/
|
||||
$uninflectedPlural = array();
|
||||
$uninflectedPlural = array('.*cash');
|
||||
/**
|
||||
* This is a key => value array of plural irregular words.
|
||||
* If key matches then the value is returned.
|
||||
|
||||
@@ -12,7 +12,9 @@ class AccountsController extends AppController {
|
||||
array('name' => 'Equity', 'url' => array('controller' => 'accounts', 'action' => 'equity')),
|
||||
array('name' => 'Income', 'url' => array('controller' => 'accounts', 'action' => 'income')),
|
||||
array('name' => 'Expense', 'url' => array('controller' => 'accounts', 'action' => 'expense')),
|
||||
array('name' => 'Bank Deposit', 'url' => array('controller' => 'accounts', 'action' => 'deposit')),
|
||||
array('name' => 'Deposits', 'header' => true),
|
||||
array('name' => 'Prior Deposits', 'url' => array('controller' => 'transactions', 'action' => 'deposit')),
|
||||
array('name' => 'New Deposit', 'url' => array('controller' => 'tenders', 'action' => 'deposit')),
|
||||
);
|
||||
|
||||
|
||||
@@ -35,77 +37,57 @@ class AccountsController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function asset() { $this->jqGridView('Asset Accounts'); }
|
||||
function liability() { $this->jqGridView('Liability Accounts'); }
|
||||
function equity() { $this->jqGridView('Equity Accounts'); }
|
||||
function income() { $this->jqGridView('Income Accounts'); }
|
||||
function expense() { $this->jqGridView('Expense Accounts'); }
|
||||
function all() { $this->jqGridView('All Accounts', 'all'); }
|
||||
function asset() { $this->gridView('Asset Accounts'); }
|
||||
function liability() { $this->gridView('Liability Accounts'); }
|
||||
function equity() { $this->gridView('Equity Accounts'); }
|
||||
function income() { $this->gridView('Income Accounts'); }
|
||||
function expense() { $this->gridView('Expense Accounts'); }
|
||||
function all() { $this->gridView('All Accounts', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
parent::jqGridDataSetup($params);
|
||||
function gridDataSetup(&$params) {
|
||||
parent::gridDataSetup($params);
|
||||
if (!isset($params['action']))
|
||||
$params['action'] = 'all';
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
return parent::jqGridDataTables($params, $model);
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
// Our count should NOT include anything extra,
|
||||
// so we need the virtual function to prevent
|
||||
// the base class from just calling our
|
||||
// gridDataTables function
|
||||
return parent::gridDataTables($params, $model);
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
function gridDataTables(&$params, &$model) {
|
||||
return array
|
||||
('link' =>
|
||||
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
|
||||
*/
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
return array_merge($fields,
|
||||
$this->Account->Ledger->LedgerEntry->debitCreditFields(true));
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
if (in_array($params['action'], array('asset', 'liability', 'equity', 'income', 'expense'))) {
|
||||
$conditions[] = array('Account.type' => strtoupper($params['action']));
|
||||
@@ -114,9 +96,9 @@ class AccountsController extends AppController {
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Account'] = array('name');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +111,11 @@ class AccountsController extends AppController {
|
||||
*/
|
||||
|
||||
function newledger($id = null) {
|
||||
if (!$this->Account->closeCurrentLedger($id)) {
|
||||
$result = $this->Account->closeCurrentLedgers($id);
|
||||
|
||||
if ($result['error']) {
|
||||
pr(compact('result'));
|
||||
die("Unable to create new ledger.");
|
||||
$this->Session->setFlash(__('Unable to create new Ledger.', true));
|
||||
}
|
||||
if ($id)
|
||||
@@ -153,8 +139,9 @@ class AccountsController extends AppController {
|
||||
}
|
||||
|
||||
$payment_accounts = $this->Account->collectableAccounts();
|
||||
//$payment_accounts[$this->Account->nameToID('Bank')] = 'Bank';
|
||||
$default_accounts = array_diff_key($payment_accounts,
|
||||
//$payment_accounts[$this->Account->nameToID('Closing')] = 'Closing';
|
||||
//$payment_accounts[$this->Account->nameToID('Equity')] = 'Equity';
|
||||
$default_accounts = array_diff_key($this->Account->paymentAccounts(),
|
||||
array($this->Account->concessionAccountID() => 1));
|
||||
$this->set(compact('payment_accounts', 'default_accounts'));
|
||||
|
||||
@@ -167,79 +154,6 @@ class AccountsController extends AppController {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: deposit
|
||||
* - Prepares the books for a bank deposit
|
||||
*/
|
||||
function deposit() {
|
||||
if ($this->data) {
|
||||
// Action the close based on provided data
|
||||
//pr($this->data);
|
||||
|
||||
// Get data about each closed ledger.
|
||||
$deposit = array('total' => 0, 'ledgers' => array());
|
||||
foreach ($this->data['Tillable']['Ledger'] AS $ledger_id => $ledger) {
|
||||
if (!$ledger['checked'])
|
||||
continue;
|
||||
|
||||
$ledger_entries =
|
||||
$this->Account->Ledger->find
|
||||
('all',
|
||||
array('link' => array
|
||||
('Account' =>
|
||||
array('fields' => array('name')),
|
||||
|
||||
'LedgerEntry' =>
|
||||
array('fields' => array('id', 'amount'),
|
||||
|
||||
'MonetarySource' =>
|
||||
array('fields' => array('name')),
|
||||
|
||||
'Customer' =>
|
||||
array('fields' => array('name')),
|
||||
|
||||
//'Transaction' =>
|
||||
//array('fields' => array('stamp')),
|
||||
),
|
||||
),
|
||||
'fields' => false,
|
||||
'conditions' => array(array('Ledger.id' => $ledger_id),
|
||||
array('LedgerEntry.id IS NOT NULL'),
|
||||
),
|
||||
));
|
||||
|
||||
$deposit['total'] += $ledger['amount'];
|
||||
$deposit['ledgers'][] = array('id' => $ledger_id,
|
||||
'name' => $ledger['account_name'],
|
||||
'total' => $ledger['amount'],
|
||||
'entries' => $ledger_entries);
|
||||
}
|
||||
|
||||
// Perform the accounting work necessary to close the
|
||||
// monetary ledgers and deposit into the bank account.
|
||||
$this->Account->closeAndDeposit($deposit['ledgers'], $this->data['Deposit']['Account']['id']);
|
||||
|
||||
$title = 'Account: Deposit Slip';
|
||||
$this->set(compact('title', 'deposit'));
|
||||
$this->render('deposit_slip');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare a close page...
|
||||
$tillable_account = $this->Account->relatedAccounts('tillable');
|
||||
$depositable_account = $this->Account->relatedAccounts('depositable');
|
||||
|
||||
foreach ($tillable_account AS &$acct) {
|
||||
$acct['Account']['stats'] = $this->Account->stats($acct['Account']['id']);
|
||||
}
|
||||
|
||||
$title = 'Account: Prepare Deposit';
|
||||
$this->set(compact('title', 'tillable_account', 'depositable_account'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -259,24 +173,20 @@ class AccountsController extends AppController {
|
||||
array('contain' =>
|
||||
array(// Models
|
||||
'CurrentLedger' =>
|
||||
array('fields' => array('id', 'sequence')),
|
||||
array('fields' => array('id', 'sequence', 'name')),
|
||||
|
||||
'Ledger' =>
|
||||
array('Close' => array
|
||||
('order' => array('Close.stamp' => 'DESC'))),
|
||||
array('CloseTransaction' => array
|
||||
('order' => array('CloseTransaction.stamp' => 'DESC'))),
|
||||
),
|
||||
'conditions' => array(array('Account.id' => $id)),
|
||||
)
|
||||
);
|
||||
|
||||
// Get all ledger entries of the CURRENT ledger
|
||||
$entries = $this->Account->findLedgerEntries($id);
|
||||
$account['CurrentLedger']['LedgerEntry'] = $entries['Entries'];
|
||||
|
||||
// Summarize each ledger
|
||||
foreach($account['Ledger'] AS &$ledger)
|
||||
$ledger = array_merge($ledger,
|
||||
$this->Account->Ledger->stats($ledger['id']));
|
||||
$entries = $this->Account->ledgerEntries($id);
|
||||
//pr(compact('entries'));
|
||||
$account['CurrentLedger']['LedgerEntry'] = $entries;
|
||||
|
||||
// Obtain stats across ALL ledgers for the summary infobox
|
||||
$stats = $this->Account->stats($id, true);
|
||||
@@ -293,5 +203,9 @@ class AccountsController extends AppController {
|
||||
$title = 'Account: ' . $account['Account']['name'];
|
||||
$this->set(compact('account', 'title', 'stats'));
|
||||
}
|
||||
|
||||
|
||||
function tst($id) {
|
||||
//$entries = $this->Account->($id);
|
||||
pr($entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,20 +23,20 @@ class ContactsController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function all() { $this->jqGridView('All Contacts', 'all'); }
|
||||
function all() { $this->gridView('All Contacts', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
$order = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
if ($index === 'Contact.last_name') {
|
||||
$order[] = 'Contact.first_name ' . $direction;
|
||||
}
|
||||
@@ -46,9 +46,9 @@ class ContactsController extends AppController {
|
||||
return $order;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Contact'] = array('id');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,27 +31,21 @@ class CustomersController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->current(); }
|
||||
function current() { $this->jqGridView('Current Tenants', 'current'); }
|
||||
function past() { $this->jqGridView('Past Tenants'); }
|
||||
function all() { $this->jqGridView('All Customers'); }
|
||||
function current() { $this->gridView('Current Tenants', 'current'); }
|
||||
function past() { $this->gridView('Past Tenants'); }
|
||||
function all() { $this->gridView('All Customers'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
parent::jqGridDataSetup($params);
|
||||
if (!isset($params['action']))
|
||||
$params['action'] = 'all';
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
return array
|
||||
('link' =>
|
||||
array(// Models
|
||||
@@ -61,39 +55,22 @@ class CustomersController extends AppController {
|
||||
);
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
$link = $this->jqGridDataCountTables($params, $model);
|
||||
$link['link']['LedgerEntry'] = array('fields' => array());
|
||||
$link['link']['LedgerEntry']['Ledger'] = array('fields' => array());
|
||||
$link['link']['LedgerEntry']['Ledger']['Account'] = array('fields' => array());
|
||||
// INNER JOIN would be great, as it would ensure we're only looking
|
||||
// at the ledger entries that we truly want. However, this also
|
||||
// removes from the query any units that do not yet have a ledger
|
||||
// entry in A/R. A solution would be to INNER JOIN these tables,
|
||||
// and LEFT JOIN it to the rest. Grouping of JOINs, however, is
|
||||
// implemented with the 'joins' tag, and is not available through
|
||||
// the Linkable behavior interface.
|
||||
//$link['link']['LedgerEntry']['Ledger']['Account']['type'] = 'INNER';
|
||||
$link['link']['LedgerEntry']['Ledger']['Account']['conditions']
|
||||
= array('Account.id' =>
|
||||
$this->Customer->LedgerEntry->Ledger->Account->accountReceivableAccountID());
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$link = $this->gridDataCountTables($params, $model);
|
||||
// StatementEntry is needed to determine customer balance
|
||||
$link['link']['StatementEntry'] = array('fields' => array());
|
||||
return $link;
|
||||
}
|
||||
|
||||
function jqGridDataFields(&$params, &$model) {
|
||||
$db = &$model->getDataSource();
|
||||
$fields = $db->fields($model, $model->alias);
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
$fields[] = ('COUNT(DISTINCT CurrentLease.id) AS lease_count');
|
||||
$fields[] = ("SUM(IF(Account.id IS NULL, 0," .
|
||||
" IF(LedgerEntry.debit_ledger_id = Account.id," .
|
||||
" 1, -1))" .
|
||||
" * IF(LedgerEntry.amount IS NULL, 0, LedgerEntry.amount))" .
|
||||
" AS 'balance'");
|
||||
return $fields;
|
||||
return array_merge($fields,
|
||||
$this->Customer->StatementEntry->chargePaymentFields(true));
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
if ($params['action'] === 'current') {
|
||||
$conditions[] = 'CurrentLease.id IS NOT NULL';
|
||||
@@ -105,71 +82,50 @@ class CustomersController extends AppController {
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
$order = array();
|
||||
$order[] = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
$order[] = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
if ($index !== 'PrimaryContact.last_name')
|
||||
$order[] = parent::jqGridDataOrder($params, $model,
|
||||
$order[] = parent::gridDataOrder($params, $model,
|
||||
'PrimaryContact.last_name', $direction);
|
||||
if ($index !== 'PrimaryContact.first_name')
|
||||
$order[] = parent::jqGridDataOrder($params, $model,
|
||||
$order[] = parent::gridDataOrder($params, $model,
|
||||
'PrimaryContact.first_name', $direction);
|
||||
if ($index !== 'Customer.id')
|
||||
$order[] = parent::jqGridDataOrder($params, $model,
|
||||
$order[] = parent::gridDataOrder($params, $model,
|
||||
'Customer.id', $direction);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
function jqGridDataRecordCount(&$params, &$model, $query) {
|
||||
function gridDataCount(&$params, &$model) {
|
||||
|
||||
if ($params['action'] != 'current')
|
||||
return parent::gridDataCount($params, $model);
|
||||
|
||||
// OK, for current customers, we have an issue.
|
||||
// We don't have a good way to use the query to obtain
|
||||
// our count. The problem is that we're relying on the
|
||||
// group by for the query, which will destroy the count,
|
||||
// whether we omit the group by or leave it in.
|
||||
// So, build a fresh query for counting.
|
||||
// group by for the query, but that simply won't work
|
||||
// for the count. However, it's not difficult to simply
|
||||
// derive it since 'current' customers are mutually
|
||||
// exclusive to 'past' customers.
|
||||
|
||||
$query['conditions'] = parent::jqGridDataConditions($params, $model);
|
||||
$tmp_params = $params;
|
||||
$tmp_params['action'] = 'all';
|
||||
$all_count = parent::gridDataCount($tmp_params, $model);
|
||||
$tmp_params['action'] = 'past';
|
||||
$past_count = parent::gridDataCount($tmp_params, $model);
|
||||
|
||||
$count = $model->find('count',
|
||||
array_merge(array('link' => array_diff_key($query['link'],
|
||||
array('CurrentLease'=>1))),
|
||||
array_diff_key($query, array('link'=>1))));
|
||||
|
||||
if ($params['action'] === 'all')
|
||||
return $count;
|
||||
|
||||
$query['conditions'][] = 'CurrentLease.id IS NULL';
|
||||
$count_past = $model->find('count', $query);
|
||||
|
||||
// Since we can't easily count 'current' directly, we
|
||||
// can quickly derive it since 'current' customers
|
||||
// are mutually exclusive to 'past' customers.
|
||||
if ($params['action'] == 'current')
|
||||
$count = $count - $count_past;
|
||||
elseif ($params['action'] == 'past') {
|
||||
$count = $count_past;
|
||||
}
|
||||
|
||||
return $count;
|
||||
// The current customer count is simply calculated
|
||||
// as all customers that are not past customers.
|
||||
return $all_count - $past_count;
|
||||
}
|
||||
|
||||
function jqGridDataRecords(&$params, &$model, $query) {
|
||||
$customers = parent::jqGridDataRecords($params, $model, $query);
|
||||
|
||||
// Get the balance on each customer.
|
||||
foreach ($customers AS &$customer) {
|
||||
$stats = $this->Customer->stats($customer['Customer']['id']);
|
||||
$customer['Customer']['balance'] = $stats['balance'];
|
||||
}
|
||||
|
||||
return $customers;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Customer'] = array('name');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -247,10 +203,18 @@ class CustomersController extends AppController {
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
|
||||
$customer = $this->Customer->details($id);
|
||||
/* //$result = $this->Customer->securityDeposits($id); */
|
||||
/* $result = $this->Customer->excessPayments($id); */
|
||||
/* //$result = $this->Customer->unreconciledCharges($id); */
|
||||
/* echo('<HR>'); */
|
||||
/* pr($result); */
|
||||
/* $this->autoRender = false; */
|
||||
/* return; */
|
||||
|
||||
$customer = $this->Customer->details($id);
|
||||
//pr($customer);
|
||||
$outstanding_balance = $customer['stats']['balance'];
|
||||
$outstanding_deposit = $customer['deposits']['summary']['balance'];
|
||||
$outstanding_deposit = $this->Customer->securityDepositBalance($id);
|
||||
|
||||
// Figure out if this customer has any non-closed leases
|
||||
$show_moveout = false;
|
||||
@@ -403,21 +367,29 @@ class CustomersController extends AppController {
|
||||
$this->Customer->recursive = -1;
|
||||
$customer = $this->Customer->read(null, $id);
|
||||
$customer = $customer['Customer'];
|
||||
$unreconciled = $this->Customer->findUnreconciledLedgerEntries($id);
|
||||
$charges = $unreconciled['debit'];
|
||||
$unreconciled = $this->Customer->unreconciledCharges($id);
|
||||
//pr($unreconciled);
|
||||
$charges = $unreconciled['entries'];
|
||||
$stats = $unreconciled['summary']['Charge'];
|
||||
// Kludge until we update receipt to have the unpaid
|
||||
// charges grid generated from a dynamic query instead
|
||||
// of simply pre-providing the list of charge IDs
|
||||
foreach ($charges AS &$charge)
|
||||
$charge['id'] = $charge['StatementEntry']['id'];
|
||||
}
|
||||
else {
|
||||
$customer = null;
|
||||
$charges = array('balance' => 0, 'entry' => array());
|
||||
$charges = array();
|
||||
$stats = array('balance' => 0);
|
||||
}
|
||||
|
||||
$A = new Account();
|
||||
$payment_accounts = $A->paymentAccounts();
|
||||
$default_account = $A->cashAccountID();
|
||||
$this->set(compact('payment_accounts', 'default_account'));
|
||||
$TT = new TenderType();
|
||||
$payment_types = $TT->paymentTypes();
|
||||
$default_type = $TT->defaultPaymentType();
|
||||
$this->set(compact('payment_types', 'default_type'));
|
||||
|
||||
$title = ($customer['name'] . ': Payment Entry');
|
||||
$this->set(compact('customer', 'charges', 'title'));
|
||||
$this->set(compact('customer', 'charges', 'stats', 'title'));
|
||||
}
|
||||
|
||||
|
||||
@@ -429,24 +401,24 @@ class CustomersController extends AppController {
|
||||
*/
|
||||
|
||||
function refund() {
|
||||
$entries = $this->Customer->LedgerEntry->find
|
||||
$entries = $this->Customer->StatementEntry->find
|
||||
('all', array
|
||||
('contain' => false,
|
||||
'conditions' => array('LedgerEntry.id' =>
|
||||
'conditions' => array('StatementEntry.id' =>
|
||||
//array(199,200,201)
|
||||
61
|
||||
),
|
||||
));
|
||||
pr(compact('entries'));
|
||||
|
||||
$this->Customer->LedgerEntry->reverse($entries);
|
||||
$this->Customer->StatementEntry->reverse($entries);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: unreconciledEntries
|
||||
* action: unreconciled
|
||||
* - returns the list of unreconciled entries
|
||||
*/
|
||||
|
||||
@@ -456,15 +428,15 @@ class CustomersController extends AppController {
|
||||
$this->layout = null;
|
||||
$this->autoLayout = false;
|
||||
$this->autoRender = false;
|
||||
Configure::write('debug', '0');
|
||||
header("Content-type: text/xml;charset=utf-8");
|
||||
//Configure::write('debug', '0');
|
||||
//header("Content-type: text/xml;charset=utf-8");
|
||||
|
||||
App::import('Helper', 'Xml');
|
||||
$xml = new XmlHelper();
|
||||
|
||||
// Find the unreconciled entries, then manipulate the structure
|
||||
// slightly to accomodate the format necessary for XML Helper.
|
||||
$unreconciled = $this->Customer->findUnreconciledLedgerEntries($id);
|
||||
$unreconciled = $this->Customer->unreconciledCharges($id);
|
||||
$unreconciled = array('entries' =>
|
||||
array_intersect_key($unreconciled['debit'],
|
||||
array('entry'=>1, 'balance'=>1)));
|
||||
@@ -473,8 +445,8 @@ class CustomersController extends AppController {
|
||||
if (!count($unreconciled['entries']['entry']))
|
||||
unset($unreconciled['entries']['entry']);
|
||||
|
||||
pr($unreconciled);
|
||||
//$reconciled = $cust->reconcileNewLedgerEntry($cust_id, 'credit', $amount);
|
||||
//pr($unreconciled);
|
||||
//$reconciled = $cust->reconcileNewStatementEntry($cust_id, 'credit', $amount);
|
||||
|
||||
$opts = array();
|
||||
//$opts['format'] = 'tags';
|
||||
|
||||
44
site/controllers/double_entries_controller.php
Normal file
44
site/controllers/double_entries_controller.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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('DebitEntry', 'CreditEntry'),
|
||||
'conditions' => array('DoubleEntry.id' => $id),
|
||||
));
|
||||
|
||||
// Prepare to render.
|
||||
$title = "Double Ledger Entry #{$entry['DoubleEntry']['id']}";
|
||||
$this->set(compact('entry', 'title'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,64 +29,46 @@ class LeasesController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function active() { $this->jqGridView('Active Leases'); }
|
||||
function closed() { $this->jqGridView('Closed Leases'); }
|
||||
function all() { $this->jqGridView('All Leases', 'all'); }
|
||||
function active() { $this->gridView('Active Leases'); }
|
||||
function closed() { $this->gridView('Closed Leases'); }
|
||||
function all() { $this->gridView('All Leases', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
parent::jqGridDataSetup($params);
|
||||
function gridDataSetup(&$params) {
|
||||
parent::gridDataSetup($params);
|
||||
if (!isset($params['action']))
|
||||
$params['action'] = 'all';
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
return array
|
||||
('link' => array('Unit' => array('fields' => array('Unit.id', 'Unit.name')),
|
||||
'Customer' => array('fields' => array('Customer.id', 'Customer.name'))));
|
||||
('link' => array('Unit' => array('fields' => array('id', 'name')),
|
||||
'Customer' => array('fields' => array('id', 'name'))));
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
$link = $this->jqGridDataCountTables($params, $model);
|
||||
$link['link']['LedgerEntry'] = array('fields' => array());
|
||||
$link['link']['LedgerEntry']['Ledger'] = array('fields' => array());
|
||||
$link['link']['LedgerEntry']['Ledger']['Account'] = array('fields' => array());
|
||||
// INNER JOIN would be great, as it would ensure we're only looking
|
||||
// at the ledger entries that we truly want. However, this also
|
||||
// removes from the query any leases that do not yet have a ledger
|
||||
// entry in A/R. A solution would be to INNER JOIN these tables,
|
||||
// and LEFT JOIN it to the rest. Grouping of JOINs, however, is
|
||||
// implemented with the 'joins' tag, and is not available through
|
||||
// the Linkable behavior interface.
|
||||
//$link['link']['LedgerEntry']['Ledger']['Account']['type'] = 'INNER';
|
||||
$link['link']['LedgerEntry']['Ledger']['Account']['conditions']
|
||||
= array('Account.id' =>
|
||||
$this->Lease->LedgerEntry->Ledger->Account->accountReceivableAccountID());
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$link = $this->gridDataCountTables($params, $model);
|
||||
$link['link']['StatementEntry'] = array('fields' => array());
|
||||
return $link;
|
||||
}
|
||||
|
||||
function jqGridDataFields(&$params, &$model) {
|
||||
$db = &$model->getDataSource();
|
||||
$fields = $db->fields($model, $model->alias);
|
||||
$fields[] = ("SUM(IF(Account.id IS NULL, 0," .
|
||||
" IF(LedgerEntry.debit_ledger_id = Account.id," .
|
||||
" 1, -1))" .
|
||||
" * IF(LedgerEntry.amount IS NULL, 0, LedgerEntry.amount))" .
|
||||
" AS 'balance'");
|
||||
return $fields;
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
return array_merge($fields,
|
||||
$this->Lease->StatementEntry->chargePaymentFields(true));
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
if ($params['action'] === 'active') {
|
||||
$conditions[] = 'Lease.close_date IS NULL';
|
||||
@@ -95,10 +77,13 @@ class LeasesController extends AppController {
|
||||
$conditions[] = 'Lease.close_date IS NOT NULL';
|
||||
}
|
||||
|
||||
if (isset($customer_id))
|
||||
$conditions[] = array('Lease.customer_id' => $customer_id);
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
// Do not sort by number, which is type varchar and
|
||||
// sorts on an ascii basis. Sort by ID instead.
|
||||
if ($index === 'Lease.number')
|
||||
@@ -109,31 +94,31 @@ class LeasesController extends AppController {
|
||||
$index = 'Unit.sort_order';
|
||||
|
||||
$order = array();
|
||||
$order[] = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
$order[] = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
// If sorting by anything other than id/number
|
||||
// add sorting by id as a secondary condition.
|
||||
if ($index !== 'Lease.id' && $index !== 'Lease.number')
|
||||
$order[] = parent::jqGridDataOrder($params, $model,
|
||||
$order[] = parent::gridDataOrder($params, $model,
|
||||
'Lease.id', $direction);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/* function jqGridRecordsPostProcess(&$params, &$model, &$records) { */
|
||||
/* function gridDataPostProcess(&$params, &$model, &$records) { */
|
||||
/* foreach ($records AS &$record) { */
|
||||
/* $record['Lease']['through_date'] */
|
||||
/* = $this->Lease->rentChargeThrough($record['Lease']['id']); */
|
||||
/* } */
|
||||
|
||||
/* parent::jqGridRecordsPostProcess($params, $model, $records); */
|
||||
/* parent::gridDataPostProcess($params, $model, $records); */
|
||||
/* } */
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Lease'] = array('number');
|
||||
$links['Unit'] = array('name');
|
||||
$links['Customer'] = array('name');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +222,67 @@ class LeasesController extends AppController {
|
||||
* to prevent feature overload on the receipt page.
|
||||
*/
|
||||
|
||||
function apply_deposit($id) {
|
||||
function apply_deposit($id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
|
||||
if ($this->data) {
|
||||
// Handle the move out based on the data given
|
||||
pr($this->data);
|
||||
$this->Lease->releaseSecurityDeposits($this->data['Lease']['id']);
|
||||
die();
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Go through the entered payments
|
||||
$receipt_transaction = array_intersect_key($this->data,
|
||||
array('Transaction'=>1,
|
||||
'transaction_id'=>1));
|
||||
foreach ($data['StatementEntry'] AS $entry) {
|
||||
// Create the receipt entry, and reconcile the credit side
|
||||
// of the double-entry (which should be A/R) as a payment.
|
||||
$ids = $this->StatementEntry->Ledger->Account->postLedgerEntry
|
||||
($receipt_transaction,
|
||||
array_intersect_key($entry, array('MonetarySource'=>1))
|
||||
+ array_intersect_key($entry, array('account_id'=>1)),
|
||||
array('debit_ledger_id' => $A->currentLedgerID($entry['account_id']),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()),
|
||||
'customer_id' => $customer_id,
|
||||
'lease_id' => $lease_id)
|
||||
+ $entry,
|
||||
'receipt');
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
$db = &$model->getDataSource();
|
||||
$fields = $db->fields($model, $model->alias);
|
||||
$fields[] = ("SUM(IF(Account.id IS NULL, 0," .
|
||||
" IF(LedgerEntry.debit_ledger_id = Account.id," .
|
||||
" 1, -1))" .
|
||||
" * IF(LedgerEntry.amount IS NULL, 0, LedgerEntry.amount))" .
|
||||
" AS 'balance'");
|
||||
return $fields;
|
||||
$receipt_transaction = array_intersect_key($ids,
|
||||
array('transaction_id'=>1,
|
||||
'split_transaction_id'=>1));
|
||||
}
|
||||
|
||||
$this->Lease->moveOut($this->data['Lease']['id'],
|
||||
'VACANT',
|
||||
$this->data['Lease']['moveout_date'],
|
||||
//true // Close this lease, if able
|
||||
false
|
||||
);
|
||||
|
||||
$this->redirect(array('controller' => 'leases',
|
||||
'action' => 'view',
|
||||
$this->data['Lease']['id']));
|
||||
$this->autoRender = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$A = new Account();
|
||||
|
||||
$lease = $this->Lease->find
|
||||
@@ -265,8 +310,8 @@ class LeasesController extends AppController {
|
||||
array('stats' => $this->Lease->stats($id)));
|
||||
|
||||
// Determine the lease security deposit
|
||||
$deposit = $this->Lease->findSecurityDeposits($lease['Lease']['id']);
|
||||
$this->set(compact('deposit'));
|
||||
$deposit_balance = $this->Lease->securityDeposits($lease['Lease']['id']);
|
||||
$this->set(compact('deposit_balance'));
|
||||
$this->set('customer', $lease['Customer']);
|
||||
$this->set('unit', $lease['Unit']);
|
||||
$this->set('lease', $lease['Lease']);
|
||||
@@ -321,8 +366,8 @@ class LeasesController extends AppController {
|
||||
array('stats' => $this->Lease->stats($id)));
|
||||
|
||||
// Determine the lease security deposit
|
||||
$deposit = $this->Lease->findSecurityDeposits($lease['Lease']['id']);
|
||||
if ($deposit['summary']['balance'] > 0)
|
||||
$deposit_balance = $this->Lease->securityDepositBalance($lease['Lease']['id']);
|
||||
if ($deposit_balance > 0)
|
||||
die("Still have un-utilized security deposit");
|
||||
|
||||
$this->set('customer', $lease['Customer']);
|
||||
@@ -351,6 +396,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->securityDeposits($id); */
|
||||
/* $outstanding_deposit = $deposits['summary']['balance']; */
|
||||
|
||||
|
||||
/* $this->set(compact('lease', 'title', */
|
||||
/* 'outstanding_deposit', */
|
||||
/* 'outstanding_balance')); */
|
||||
}
|
||||
|
||||
|
||||
@@ -453,8 +510,7 @@ class LeasesController extends AppController {
|
||||
$outstanding_balance = $lease['Lease']['stats']['balance'];
|
||||
|
||||
// Determine the lease security deposit
|
||||
$deposits = $this->Lease->findSecurityDeposits($lease['Lease']['id']);
|
||||
$outstanding_deposit = $deposits['summary']['balance'];
|
||||
$outstanding_deposit = $this->Lease->securityDepositBalance($lease['Lease']['id']);
|
||||
|
||||
// Set up dynamic menu items
|
||||
if (!isset($lease['Lease']['close_date'])) {
|
||||
|
||||
@@ -19,300 +19,81 @@ class LedgerEntriesController extends AppController {
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData 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) {
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$link =
|
||||
array(// Models
|
||||
'Transaction' =>
|
||||
array('fields' => array('id', 'stamp'),
|
||||
),
|
||||
'Transaction' =>
|
||||
array('fields' => array('id', 'stamp'),
|
||||
),
|
||||
|
||||
'MonetarySource' =>
|
||||
array('fields' => array('id', 'name'),
|
||||
),
|
||||
'Ledger' =>
|
||||
array('fields' => array('id', 'sequence'),
|
||||
'Account' =>
|
||||
array('fields' => array('id', 'name', 'type'),
|
||||
),
|
||||
),
|
||||
|
||||
'Customer' =>
|
||||
array('fields' => array('id', 'name'),
|
||||
),
|
||||
'Tender' =>
|
||||
array('fields' => array('id', 'name', 'nsf_transaction_id'),
|
||||
),
|
||||
|
||||
'Lease' =>
|
||||
array('fields' => array('id', 'number'),
|
||||
'Unit' =>
|
||||
array('fields' => array('id', 'name'),
|
||||
),
|
||||
),
|
||||
/* 'DebitEntry', */
|
||||
/* 'CreditEntry', */
|
||||
);
|
||||
|
||||
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 gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
return array_merge($fields,
|
||||
$this->LedgerEntry->debitCreditFields());
|
||||
}
|
||||
|
||||
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);
|
||||
function gridDataFilterTablesTable(&$params, &$model, $table) {
|
||||
$table = $this->gridDataFilterTableName($params, $model, $table);
|
||||
// Account is already part of our standard table set.
|
||||
// Ensure we don't add it in again as part of filtering.
|
||||
if ($table == 'Account')
|
||||
return null;
|
||||
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
// Customer needs to be added beneath Transaction
|
||||
if ($table == 'Customer')
|
||||
return 'Transaction';
|
||||
|
||||
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;
|
||||
return $table;
|
||||
}
|
||||
|
||||
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 gridDataFilterTablesConfig(&$params, &$model, $table) {
|
||||
$config = parent::gridDataFilterTablesConfig($params, $model, $table);
|
||||
|
||||
// Customer is special in that its linked in by Transaction
|
||||
// Therefore, the actual table used for the join is 'Transaction',
|
||||
// not 'Customer', and so we need to specify Customer here.
|
||||
if ($table == 'Customer')
|
||||
$config = array('Customer' => $config);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
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 gridDataFilterConditionsStatement(&$params, &$model, $table, $key, $value) {
|
||||
//pr(compact('table', 'key', 'value'));
|
||||
if ($table == 'Account' && $value['value_present'] && $value['value'] === '-AR-')
|
||||
$value = $this->LedgerEntry->Ledger->Account->accountReceivableAccountID();
|
||||
return parent::gridDataFilterConditionsStatement($params, $model, $table, $key, $value);
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
/* if ($index === 'balance') */
|
||||
/* return ($index .' '. $direction); */
|
||||
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
$order = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
if ($index === 'Transaction.stamp') {
|
||||
$order[] = 'LedgerEntry.id ' . $direction;
|
||||
@@ -321,28 +102,28 @@ class LedgerEntriesController extends AppController {
|
||||
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'];
|
||||
function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) {
|
||||
parent::gridDataPostProcessCalculatedFields($params, $model, $records);
|
||||
foreach ($records AS &$record) {
|
||||
// REVISIT <AP>: 20090730
|
||||
// We really need the grid to handle this. We probably need to
|
||||
// either create a hidden column with the nsf id, or pass back
|
||||
// a list of nsf items as user data. We can then add an onload
|
||||
// function to sweep through the nsf items and format them.
|
||||
// For now... this works.
|
||||
if (!empty($record['Tender']['nsf_transaction_id']))
|
||||
$record['Tender']['name'] =
|
||||
'<SPAN class="nsf-tender">' . $record['Tender']['name'] . '</SPAN>';
|
||||
}
|
||||
|
||||
return parent::jqGridDataRecords($params, $model, $query);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: reverse the ledger entry
|
||||
*/
|
||||
|
||||
function reverse($id) {
|
||||
$this->LedgerEntry->reverse($id);
|
||||
$this->redirect(array('action'=>'view', $id));
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['LedgerEntry'] = array('id');
|
||||
$links['Transaction'] = array('id');
|
||||
$links['Ledger'] = array('id');
|
||||
$links['Account'] = array('controller' => 'accounts', 'name');
|
||||
$links['Tender'] = array('name');
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -359,109 +140,50 @@ class LedgerEntriesController extends AppController {
|
||||
$this->redirect(array('controller' => 'accounts', 'action'=>'index'));
|
||||
}
|
||||
|
||||
// Get the LedgerEntry and related fields
|
||||
// Get the Entry 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',
|
||||
),
|
||||
array('contain' => array
|
||||
(
|
||||
'Transaction' =>
|
||||
array('fields' => array('id', 'stamp'),
|
||||
),
|
||||
|
||||
'fields' => array('LedgerEntry.*'),
|
||||
'Ledger' =>
|
||||
array('fields' => array('id', 'sequence', 'name'),
|
||||
'Account' =>
|
||||
array('fields' => array('id', 'name', 'type'),
|
||||
),
|
||||
),
|
||||
|
||||
'Tender' =>
|
||||
array('fields' => array('id', 'name'),
|
||||
),
|
||||
|
||||
'DebitEntry' => array('fields' => array('id', 'crdr')),
|
||||
'CreditEntry' => array('fields' => array('id', 'crdr')),
|
||||
),
|
||||
|
||||
'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.
|
||||
if (!empty($entry['DebitEntry']) && !empty($entry['CreditEntry']))
|
||||
die("LedgerEntry has both a matching DebitEntry and CreditEntry");
|
||||
if (empty($entry['DebitEntry']) && empty($entry['CreditEntry']))
|
||||
die("LedgerEntry has neither a matching DebitEntry nor a CreditEntry");
|
||||
if (empty($entry['DebitEntry']) && count($entry['CreditEntry']) != 1)
|
||||
die("LedgerEntry has more than one CreditEntry");
|
||||
if (empty($entry['CreditEntry']) && count($entry['DebitEntry']) != 1)
|
||||
die("LedgerEntry has more than one DebitEntry");
|
||||
|
||||
// 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));
|
||||
}
|
||||
if (empty($entry['DebitEntry']))
|
||||
$entry['MatchingEntry'] = $entry['CreditEntry'][0];
|
||||
else
|
||||
$entry['MatchingEntry'] = $entry['DebitEntry'][0];
|
||||
|
||||
// Prepare to render.
|
||||
$title = "Double Ledger Entry #{$entry['LedgerEntry']['id']}";
|
||||
$this->set(compact('entry', 'title', 'reconciled', 'stats'));
|
||||
$title = "Ledger Entry #{$entry['LedgerEntry']['id']}";
|
||||
$this->set(compact('entry', 'title'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,78 +29,73 @@ class LedgersController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function current() { $this->jqGridView('Current Ledgers'); }
|
||||
function closed() { $this->jqGridView('Closed Ledgers'); }
|
||||
function all() { $this->jqGridView('All Ledgers', 'all'); }
|
||||
function current() { $this->gridView('Current Ledgers'); }
|
||||
function closed() { $this->gridView('Closed Ledgers'); }
|
||||
function all() { $this->gridView('All Ledgers', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
parent::jqGridDataSetup($params);
|
||||
function gridDataSetup(&$params) {
|
||||
parent::gridDataSetup($params);
|
||||
if (!isset($params['action']))
|
||||
$params['action'] = 'all';
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
return array('contain' => false);
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
// Our count should NOT include anything extra,
|
||||
// so we need the virtual function to prevent
|
||||
// the base class from just calling our
|
||||
// gridDataTables function.
|
||||
return parent::gridDataTables($params, $model);
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
function gridDataTables(&$params, &$model) {
|
||||
return array
|
||||
('link' =>
|
||||
array(// Models
|
||||
'Account',
|
||||
'LedgerEntry',
|
||||
'Close',
|
||||
'CloseTransaction',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
$fields[] = 'CONCAT(Account.id, "-", Ledger.sequence) AS id_sequence';
|
||||
return array_merge($fields,
|
||||
$this->Ledger->LedgerEntry->debitCreditFields(true));
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
if ($params['action'] === 'current') {
|
||||
$conditions[] = array('NOT' => array('Ledger.closed'));
|
||||
$conditions[] = array('Ledger.close_transaction_id' => null);
|
||||
}
|
||||
elseif ($params['action'] === 'closed') {
|
||||
$conditions[] = 'Ledger.closed';
|
||||
$conditions[] = array('Ledger.close_transaction_id !=' => null);
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
$id_sequence = false;
|
||||
if ($index === 'id_sequence') {
|
||||
$id_sequence = true;
|
||||
$index = 'Ledger.account_id';
|
||||
}
|
||||
|
||||
$order = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
$order = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
if ($id_sequence) {
|
||||
$order[] = 'Ledger.sequence ' . $direction;
|
||||
@@ -109,10 +104,10 @@ class LedgersController extends AppController {
|
||||
return $order;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Ledger'] = array('id_sequence');
|
||||
$links['Account'] = array('name');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,28 +15,28 @@ class MapsController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function all() { $this->jqGridView('All Maps', 'all'); }
|
||||
function all() { $this->gridView('All Maps', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
function gridDataTables(&$params, &$model) {
|
||||
return array
|
||||
('link' => array('SiteArea' => array('fields' => array('SiteArea.id', 'SiteArea.name')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Map'] = array('id');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
|
||||
class MonetarySourcesController 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);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: index / all
|
||||
* - Generate a listing of MonetarySources
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function all() { $this->jqGridView('All MonetarySources', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
return array
|
||||
('contain' => false,
|
||||
);
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
$links['MonetarySource'] = array('id');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: nsf
|
||||
* - Marks a monetary source as having insufficient funds.
|
||||
*/
|
||||
|
||||
function nsf($id = null) {
|
||||
if (!$id) {
|
||||
$this->Session->setFlash(__('Invalid Item.', true));
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
|
||||
// REVISIT <AP>: 20090713
|
||||
// For testing purposes, must be deleted
|
||||
$stamp = '2009-07-09';
|
||||
|
||||
$this->MonetarySource->nsf($id, $stamp);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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 MonetarySource and related fields
|
||||
$monetary_source = $this->MonetarySource->find
|
||||
('first', array
|
||||
('contain' => false,
|
||||
));
|
||||
|
||||
// REVISIT <AP>: 20090713
|
||||
// Consider allowing the NSF operation only if the source is used on
|
||||
// a ledger entry that is debited on a "payable" account (perhaps
|
||||
// even restricted to "payable" ASSET accounts), credited on Receipt
|
||||
// (or A/R), and reconciles the credit to an entry that debits on a
|
||||
// "depositable" account.
|
||||
|
||||
// Set up dynamic menu items
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'Operations', 'header' => true);
|
||||
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'NSF',
|
||||
'url' => array('action' => 'nsf',
|
||||
$id));
|
||||
|
||||
// Prepare to render.
|
||||
$title = "Monetary Source #{$monetary_source['MonetarySource']['id']}";
|
||||
$this->set(compact('monetary_source', 'title'));
|
||||
}
|
||||
}
|
||||
279
site/controllers/statement_entries_controller.php
Normal file
279
site/controllers/statement_entries_controller.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
class StatementEntriesController 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: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function gridDataCountTables(&$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 (isset($params['post']['custom']['statement_entry_id'])) {
|
||||
$link['PaymentEntry'] = array();
|
||||
$link['ChargeEntry'] = array();
|
||||
}
|
||||
|
||||
if (isset($params['post']['custom']['account_id'])) {
|
||||
$link['LedgerEntry'] = array('fields' => array('id'));
|
||||
$link['LedgerEntry']['Account'] = array('fields' => array('id', 'name', 'type'));
|
||||
}
|
||||
|
||||
/* if (count(array_intersect($params['fields'], array('applied'))) == 1) { */
|
||||
/* $link['PaymentEntry'] = array(); */
|
||||
/* $link['ChargeEntry'] = array(); */
|
||||
/* } */
|
||||
|
||||
return array('link' => $link);
|
||||
}
|
||||
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$tables = $this->gridDataCountTables($params, $model);
|
||||
$tables['link']['LedgerEntry'] = array('fields' => array('id'));
|
||||
$tables['link']['LedgerEntry']['Account'] = array('fields' => array('id', 'name', 'type'));
|
||||
return $tables;
|
||||
}
|
||||
|
||||
function gridDataFields(&$params, &$model) {
|
||||
//foreach(
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
|
||||
$fields[] = "COUNT(LedgerEntry.id) AS ledger_entry_count";
|
||||
|
||||
if (in_array('applied', $params['post']['fields'])) {
|
||||
$fields[] = ("IF(StatementEntry.type = 'CHARGE'," .
|
||||
" SUM(COALESCE(PaymentEntry.amount,0))," .
|
||||
" SUM(COALESCE(ChargeEntry.amount,0)))" .
|
||||
" AS 'applied'");
|
||||
$fields[] = ("StatementEntry.amount - (" .
|
||||
"IF(StatementEntry.type = 'CHARGE'," .
|
||||
" SUM(COALESCE(PaymentEntry.amount,0))," .
|
||||
" SUM(COALESCE(ChargeEntry.amount,0)))" .
|
||||
") AS 'balance'");
|
||||
}
|
||||
|
||||
$fields = array_merge($fields,
|
||||
$this->StatementEntry->chargePaymentFields());
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
extract($params['post']['custom']);
|
||||
|
||||
if (!empty($from_date))
|
||||
$conditions[]
|
||||
= array('Transaction.stamp >=' =>
|
||||
$this->StatementEntry->Transaction->dateFormatBeforeSave($from_date));
|
||||
|
||||
if (!empty($through_date))
|
||||
$conditions[]
|
||||
= array('Transaction.stamp <=' =>
|
||||
$this->StatementEntry->Transaction->dateFormatBeforeSave($through_date . ' 23:59:59'));
|
||||
|
||||
if (isset($account_id))
|
||||
$conditions[] = array('LedgerEntry.account_id' => $account_id);
|
||||
|
||||
if (isset($statement_entry_id)) {
|
||||
$conditions[] = array('OR' =>
|
||||
array(array('ChargeEntry.id' => $statement_entry_id),
|
||||
array('PaymentEntry.id' => $statement_entry_id)));
|
||||
}
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) {
|
||||
parent::gridDataPostProcessCalculatedFields($params, $model, $records);
|
||||
foreach ($records AS &$record) {
|
||||
if ($record['StatementEntry']['ledger_entry_count'] > 1)
|
||||
$record['Account']['name'] = 'Multiple';
|
||||
}
|
||||
}
|
||||
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['StatementEntry'] = array('id');
|
||||
$links['Transaction'] = array('id');
|
||||
$links['Account'] = array('name');
|
||||
$links['Customer'] = array('name');
|
||||
$links['Lease'] = array('number');
|
||||
$links['Unit'] = array('name');
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
$order = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
// After sorting by whatever the user wants, add these
|
||||
// defaults into the sort mechanism. If we're already
|
||||
// sorting by one of them, it will only be redundant,
|
||||
// and should cause no harm (possible a longer query?)
|
||||
$order[] = 'Transaction.stamp ' . $direction;
|
||||
$order[] = 'StatementEntry.effective_date ' . $direction;
|
||||
$order[] = 'StatementEntry.id ' . $direction;
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
function gridDataRecordsExecute(&$params, &$model, $query) {
|
||||
if (in_array('applied', $params['post']['fields'])) {
|
||||
$tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1));
|
||||
$tquery['fields'] = array("IF(StatementEntry.type = 'CHARGE'," .
|
||||
" SUM(COALESCE(StatementFraction.amount,0))," .
|
||||
" SUM(COALESCE(ChargeEntry.amount,0)))" .
|
||||
" AS 'applied'",
|
||||
|
||||
"StatementEntry.amount - (" .
|
||||
"IF(StatementEntry.type = 'CHARGE'," .
|
||||
" SUM(COALESCE(PaymentEntry.amount,0))," .
|
||||
" SUM(COALESCE(ChargeEntry.amount,0)))" .
|
||||
") AS 'balance'",
|
||||
);
|
||||
|
||||
//pr(compact('tquery'));
|
||||
$total = $model->find('first', $tquery);
|
||||
$params['userdata']['total'] = $total[0]['applied'];
|
||||
$params['userdata']['balance'] = $total[0]['balance'];
|
||||
}
|
||||
else {
|
||||
$tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1));
|
||||
$tquery['fields'] = array("SUM(COALESCE(StatementEntry.amount,0)) AS 'total'");
|
||||
$total = $model->find('first', $tquery);
|
||||
$params['userdata']['total'] = $total[0]['total'];
|
||||
}
|
||||
|
||||
return parent::gridDataRecordsExecute($params, $model, $query);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: reverse the ledger entry
|
||||
*/
|
||||
|
||||
function reverse($id) {
|
||||
$this->StatementEntry->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 StatementEntry and related fields
|
||||
$this->StatementEntry->prClassLevel(30, 'Model');
|
||||
$entry = $this->StatementEntry->find
|
||||
('first',
|
||||
array('contain' => array
|
||||
('Transaction' => array('fields' => array('id', 'stamp')),
|
||||
'Customer' => array('fields' => array('id', 'name')),
|
||||
'Lease' => array('fields' => array('id')),
|
||||
'LedgerEntry' => array('fields' => array('id'),
|
||||
'Account' => array('id', 'name', 'type')),
|
||||
),
|
||||
|
||||
'conditions' => array('StatementEntry.id' => $id),
|
||||
));
|
||||
pr($entry);
|
||||
//die();
|
||||
|
||||
$reconciled = $this->StatementEntry->reconciledEntries($id);
|
||||
|
||||
|
||||
/* // REVISIT <AP>: 20090711 */
|
||||
/* // It's not clear whether we should be able to reverse charges that have */
|
||||
/* // already been paid/cleared/reconciled. Certainly, that will be the */
|
||||
/* // case when someone has pre-paid and then moves out early. However, this */
|
||||
/* // will work well for items accidentally charged but not yet paid for. */
|
||||
/* if ((!$entry['DebitLedger']['Account']['trackable'] || */
|
||||
/* $stats['debit']['amount_reconciled'] == 0) && */
|
||||
/* (!$entry['CreditLedger']['Account']['trackable'] || */
|
||||
/* $stats['credit']['amount_reconciled'] == 0) */
|
||||
|
||||
/* && 0 */
|
||||
|
||||
/* ) */
|
||||
/* { */
|
||||
/* // Set up dynamic menu items */
|
||||
/* $this->sidemenu_links[] = */
|
||||
/* array('name' => 'Operations', 'header' => true); */
|
||||
|
||||
/* $this->sidemenu_links[] = */
|
||||
/* array('name' => 'Undo', */
|
||||
/* 'url' => array('action' => 'reverse', */
|
||||
/* $id)); */
|
||||
/* } */
|
||||
|
||||
/* if ($this->StatementEntry->Ledger->Account->type */
|
||||
/* ($entry['CreditLedger']['Account']['id']) == 'INCOME') */
|
||||
/* { */
|
||||
/* // Set up dynamic menu items */
|
||||
/* $this->sidemenu_links[] = */
|
||||
/* array('name' => 'Operations', 'header' => true); */
|
||||
|
||||
/* $this->sidemenu_links[] = */
|
||||
/* array('name' => 'Reverse', */
|
||||
/* 'url' => array('action' => 'reverse', */
|
||||
/* $id)); */
|
||||
/* } */
|
||||
|
||||
$stats = $this->StatementEntry->stats($id);
|
||||
|
||||
if (strtoupper($entry['StatementEntry']['type']) === 'CHARGE')
|
||||
$stats = $stats['Charge'];
|
||||
else
|
||||
$stats = $stats['Payment'];
|
||||
|
||||
// Prepare to render.
|
||||
$title = "Statement Entry #{$entry['StatementEntry']['id']}";
|
||||
$this->set(compact('entry', 'title', 'reconciled', 'stats'));
|
||||
}
|
||||
|
||||
}
|
||||
167
site/controllers/tenders_controller.php
Normal file
167
site/controllers/tenders_controller.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
class TendersController 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);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: index / all
|
||||
* - Generate a listing of Tenders
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function all() { $this->gridView('All Legal Tender', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function gridDataTables(&$params, &$model) {
|
||||
return array
|
||||
('link' =>
|
||||
array('TenderType',
|
||||
'Customer',
|
||||
'LedgerEntry' =>
|
||||
array('Transaction',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function gridDataRecordsExecute(&$params, &$model, $query) {
|
||||
$tquery = array_diff_key($query, array('fields'=>1,'group'=>1,'limit'=>1,'order'=>1));
|
||||
$tquery['fields'] = array("SUM(COALESCE(LedgerEntry.amount,0)) AS 'total'");
|
||||
$total = $model->find('first', $tquery);
|
||||
$params['userdata']['total'] = $total[0]['total'];
|
||||
|
||||
return parent::gridDataRecordsExecute($params, $model, $query);
|
||||
}
|
||||
|
||||
function gridDataPostProcessCalculatedFields(&$params, &$model, &$records) {
|
||||
parent::gridDataPostProcessCalculatedFields($params, $model, $records);
|
||||
foreach ($records AS &$record) {
|
||||
// REVISIT <AP>: 20090730
|
||||
// We really need the grid to handle this. We probably need to
|
||||
// either create a hidden column with the nsf id, or pass back
|
||||
// a list of nsf items as user data. We can then add an onload
|
||||
// function to sweep through the nsf items and format them.
|
||||
// For now... this works.
|
||||
if (!empty($record['Tender']['nsf_transaction_id']))
|
||||
$record['Tender']['name'] =
|
||||
'<SPAN class="nsf-tender">' . $record['Tender']['name'] . '</SPAN>';
|
||||
}
|
||||
}
|
||||
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Tender'] = array('name', 'id');
|
||||
$links['Customer'] = array('name');
|
||||
$links['TenderType'] = array('name');
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: deposit
|
||||
* - Prepares the books for a bank deposit
|
||||
*/
|
||||
|
||||
function deposit() {
|
||||
// Prepare a close page...
|
||||
$deposit_types = $this->Tender->TenderType->depositTypes(
|
||||
// Testing... limit to only one type
|
||||
//array('limit' => 1)
|
||||
);
|
||||
$deposit_accounts = $this->Tender->TenderType->Account->depositAccounts();
|
||||
|
||||
foreach ($deposit_types AS $type_id => &$type)
|
||||
$type = array('id' => $type_id,
|
||||
'name' => $type,
|
||||
'stats' => $this->Tender->TenderType->stats($type_id));
|
||||
|
||||
//pr(compact('deposit_types', 'deposit_accounts'));
|
||||
|
||||
$title = 'Prepare Deposit';
|
||||
$this->set(compact('title', 'deposit_types', 'deposit_accounts'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: nsf
|
||||
* - Marks a tender as having insufficient funds.
|
||||
*/
|
||||
|
||||
function nsf($id = null) {
|
||||
if (!$id) {
|
||||
$this->Session->setFlash(__('Invalid Item.', true));
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
|
||||
$this->Tender->nsf($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 Tender and related fields
|
||||
$tender = $this->Tender->find
|
||||
('first', array
|
||||
('contain' => array('TenderType', 'Customer', 'LedgerEntry' => array('Transaction')),
|
||||
));
|
||||
|
||||
|
||||
if (!empty($tender['Tender']['deposit_transaction_id'])
|
||||
&& empty($tender['Tender']['nsf_transaction_id'])
|
||||
// Hard to tell what types of items can come back as NSF.
|
||||
// For now, assume iff it is a named item, it can be NSF.
|
||||
&& !empty($tender['TenderType']['data1_name'])
|
||||
) {
|
||||
// Set up dynamic menu items
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'Operations', 'header' => true);
|
||||
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'NSF',
|
||||
'url' => array('action' => 'nsf',
|
||||
$id));
|
||||
}
|
||||
|
||||
// Prepare to render.
|
||||
$title = "Tender #{$tender['Tender']['id']}";
|
||||
$this->set(compact('tender', 'title'));
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ class TransactionsController extends AppController {
|
||||
var $sidemenu_links =
|
||||
array(array('name' => 'Transactions', 'header' => true),
|
||||
array('name' => 'All', 'url' => array('controller' => 'transactions', 'action' => 'all')),
|
||||
array('name' => 'Invoices', 'url' => array('controller' => 'transactions', 'action' => 'invoice')),
|
||||
array('name' => 'Receipts', 'url' => array('controller' => 'transactions', 'action' => 'receipt')),
|
||||
array('name' => 'Deposits', 'url' => array('controller' => 'transactions', 'action' => 'deposit')),
|
||||
);
|
||||
|
||||
|
||||
@@ -29,70 +32,53 @@ class TransactionsController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function all() { $this->jqGridView('All Transactions', 'all'); }
|
||||
function all() { $this->gridView('All Transactions', 'all'); }
|
||||
function invoice() { $this->gridView('Invoices'); }
|
||||
function receipt() { $this->gridView('Receipts'); }
|
||||
function deposit() { $this->gridView('Deposits'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Transaction'] = array('id');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
return parent::gridDataTables($params, $model);
|
||||
}
|
||||
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$link = $this->gridDataCountTables($params, $model);
|
||||
$link['link']['StatementEntry'] = array('fields' => array());
|
||||
$link['link']['DepositTender'] = array('fields' => array());
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: view
|
||||
* - Displays information about a specific transaction
|
||||
*/
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
//$fields[] = 'COUNT(StatementEntry.id) AS entries';
|
||||
$fields[] = ("IF(Transaction.type = 'DEPOSIT'," .
|
||||
" COUNT(DepositTender.id)," .
|
||||
" COUNT(StatementEntry.id)) AS entries");
|
||||
return $fields;
|
||||
}
|
||||
|
||||
function view($id = null) {
|
||||
if (!$id) {
|
||||
$this->Session->setFlash(__('Invalid Item.', true));
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
$transaction = $this->Transaction->find
|
||||
('first',
|
||||
array('contain' =>
|
||||
array(// Models
|
||||
'LedgerEntry' => array('fields' => array('LedgerEntry.id',
|
||||
'LedgerEntry.amount',
|
||||
'LedgerEntry.comment'),
|
||||
//Models
|
||||
if (in_array($params['action'], array('invoice', 'receipt', 'deposit')))
|
||||
$conditions[] = array('Transaction.type' => strtoupper($params['action']));
|
||||
|
||||
'DebitLedger' => array
|
||||
('fields' => array('DebitLedger.id', 'DebitLedger.sequence'),
|
||||
'Account' => array
|
||||
('fields' => array('Account.id', 'Account.name')),
|
||||
),
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
'CreditLedger' => array
|
||||
('fields' => array('CreditLedger.id', 'CreditLedger.sequence'),
|
||||
'Account' => array
|
||||
('fields' => array('Account.id', 'Account.name')),
|
||||
),
|
||||
),
|
||||
),
|
||||
'conditions' => array('Transaction.id' => $id),
|
||||
));
|
||||
|
||||
// Figure out the transaction total
|
||||
$total = 0;
|
||||
foreach($transaction['LedgerEntry'] AS $entry)
|
||||
$total += $entry['amount'];
|
||||
|
||||
// OK, prepare to render.
|
||||
$title = 'Transaction #' . $transaction['Transaction']['id'];
|
||||
$this->set(compact('transaction', 'title', 'total'));
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Transaction'] = array('id');
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,14 +123,12 @@ class TransactionsController extends AppController {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($this->data['LedgerEntry'] AS &$entry) {
|
||||
if (!isset($entry['acct'][$entry['account_id']]))
|
||||
continue;
|
||||
|
||||
$entry['MonetarySource'] = $entry['acct'][$entry['account_id']];
|
||||
foreach($this->data['Entry'] AS &$entry) {
|
||||
$entry['Tender'] = $entry['type'][$entry['tender_type_id']];
|
||||
unset($entry['type']);
|
||||
unset($entry['tender_type_id']);
|
||||
}
|
||||
|
||||
pr($this->data);
|
||||
if (!$this->Transaction->addReceipt($this->data,
|
||||
$this->data['Customer']['id'],
|
||||
(isset($this->data['Lease']['id'])
|
||||
@@ -160,7 +144,229 @@ class TransactionsController extends AppController {
|
||||
$this->layout = null;
|
||||
$this->autoLayout = false;
|
||||
$this->autoRender = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: postDeposit
|
||||
* - handles the creation of a deposit transaction
|
||||
*/
|
||||
|
||||
function postDeposit() {
|
||||
if (!$this->RequestHandler->isPost()) {
|
||||
echo('<H2>THIS IS NOT A POST FOR SOME REASON</H2>');
|
||||
return;
|
||||
}
|
||||
|
||||
//pr($this->data);
|
||||
|
||||
// Go through each type of tender presented to the user
|
||||
// Determine which are to be deposited, and which are to
|
||||
// have their corresponding account ledgers closed.
|
||||
$deposit_tender_ids = array();
|
||||
$deposit_type_ids = array();
|
||||
$close_type_ids = array();
|
||||
foreach ($this->data['TenderType'] AS $type_id => $type) {
|
||||
$type['items'] = unserialize($type['items']);
|
||||
if (empty($type['selection']) ||
|
||||
$type['selection'] === 'none' ||
|
||||
($type['selection'] === 'subset' && count($type['items']) == 0))
|
||||
continue;
|
||||
|
||||
// The deposit includes either the whole type, or just certain tenders
|
||||
if ($type['selection'] === 'all')
|
||||
$deposit_type_ids[] = $type_id;
|
||||
else
|
||||
$deposit_tender_ids = array_merge($deposit_tender_ids, $type['items']);
|
||||
|
||||
// Should we close the ledger for this tender type?
|
||||
// First, the user would have to request that we do so,
|
||||
// but additionally, we shouldn't close a ledger unless
|
||||
// all the tenders are included in this deposit. That
|
||||
// doesn't guarantee that the ledger has a zero balance,
|
||||
// but it does carry the balance forward, and a total
|
||||
// deposit would imply a fresh start, so go for it.
|
||||
if (!empty($type['close']) && $type['selection'] === 'all')
|
||||
$close_type_ids[] = $type_id;
|
||||
}
|
||||
|
||||
// Make sure we actually have something to deposit
|
||||
if (empty($deposit_type_ids) && empty($deposit_tender_ids)) {
|
||||
$this->Session->setFlash(__('Nothing to Deposit', true));
|
||||
$this->redirect(array('controller' => 'tenders', 'action'=>'deposit'));
|
||||
}
|
||||
|
||||
// Build up a set of conditions based on user selection
|
||||
$deposit_conditions = array();
|
||||
if (!empty($deposit_type_ids))
|
||||
$deposit_conditions[] = array('TenderType.id' => $deposit_type_ids);
|
||||
if (!empty($deposit_tender_ids))
|
||||
$deposit_conditions[] = array('DepositTender.id' => $deposit_tender_ids);
|
||||
|
||||
// Add in confirmation that items have not already been deposited
|
||||
$deposit_conditions =
|
||||
array(array('DepositTender.deposit_transaction_id' => null),
|
||||
array('OR' => $deposit_conditions));
|
||||
|
||||
// Lookup the items to be deposited
|
||||
$tenders = $this->Transaction->DepositTender->find
|
||||
('all',
|
||||
array('contain' => array('TenderType', 'LedgerEntry'),
|
||||
'conditions' => $deposit_conditions,
|
||||
));
|
||||
|
||||
// Build the deposit transaction
|
||||
$deposit = array('Transaction' => array(), 'Entry' => array());
|
||||
foreach ($tenders AS $tender) {
|
||||
$deposit['Entry'][] =
|
||||
array('tender_id' => $tender['DepositTender']['id'],
|
||||
'account_id' => $tender['LedgerEntry']['account_id'],
|
||||
'amount' => $tender['LedgerEntry']['amount'],
|
||||
);
|
||||
}
|
||||
|
||||
//pr(compact('deposit_type_ids', 'deposit_tender_ids', 'close_type_ids', 'deposit_conditions', 'deposit'));
|
||||
|
||||
// OK, perform the deposit and associated accounting
|
||||
$result = $this->Transaction->addDeposit
|
||||
($deposit, $this->data['Deposit']['Account']['id']);
|
||||
//pr(compact('deposit', 'result'));
|
||||
|
||||
// Close any ledgers necessary
|
||||
if (!empty($close_type_ids)) {
|
||||
// Find the accounts associated with the types to close ...
|
||||
$accounts = $this->Transaction->DepositTender->find
|
||||
('all',
|
||||
array('contain' => array('TenderType.account_id'),
|
||||
'conditions' => array(array('TenderType.id' => $close_type_ids)),
|
||||
));
|
||||
|
||||
// ... and close them
|
||||
$this->Transaction->Account->closeCurrentLedgers
|
||||
(array_map(create_function('$item', 'return $item["TenderType"]["account_id"];'), $accounts));
|
||||
}
|
||||
|
||||
// Look out for errors
|
||||
if ($result['error']) {
|
||||
$this->Session->setFlash(__('Unable to Create Deposit', true));
|
||||
$this->redirect(array('controller' => 'tenders', 'action'=>'deposit'));
|
||||
}
|
||||
|
||||
// Present the deposit slip to the user
|
||||
$this->redirect(array('controller' => 'transactions',
|
||||
'action' => 'deposit_slip',
|
||||
$result['transaction_id']));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: view
|
||||
* - Displays information about a specific transaction
|
||||
*/
|
||||
|
||||
function view($id = null) {
|
||||
if (!$id) {
|
||||
$this->Session->setFlash(__('Invalid Item.', true));
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
|
||||
$transaction = $this->Transaction->find
|
||||
('first',
|
||||
array('contain' =>
|
||||
array(// Models
|
||||
'Account' =>
|
||||
array('fields' => array('Account.id',
|
||||
'Account.name'),
|
||||
),
|
||||
|
||||
'Ledger' =>
|
||||
array('fields' => array('Ledger.id',
|
||||
'Ledger.name'),
|
||||
),
|
||||
),
|
||||
'conditions' => array('Transaction.id' => $id),
|
||||
));
|
||||
|
||||
if ($transaction['Transaction']['type'] === 'DEPOSIT') {
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'Operations', 'header' => true);
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'View Slip', 'url' => array('action' => 'deposit_slip', $id));
|
||||
}
|
||||
|
||||
// OK, prepare to render.
|
||||
$title = 'Transaction #' . $transaction['Transaction']['id'];
|
||||
$this->set(compact('transaction', 'title'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: deposit_slip
|
||||
* - Special presentation
|
||||
* Processes the user input and updates the database
|
||||
*/
|
||||
|
||||
function deposit_slip($id) {
|
||||
// Build a container for the deposit slip data
|
||||
$deposit = array('types' => array());
|
||||
|
||||
$this->id = $id;
|
||||
$deposit +=
|
||||
$this->Transaction->find('first', array('contain' => false));
|
||||
|
||||
// Get a summary of all forms of tender in the deposit
|
||||
$result = $this->Transaction->find
|
||||
('all',
|
||||
array('link' => array('DepositTender' =>
|
||||
array('fields' => array(),
|
||||
'TenderType',
|
||||
'LedgerEntry' =>
|
||||
array('fields' => array()))),
|
||||
'fields' => array(//'TenderType.id', 'TenderType.name',
|
||||
"COUNT(DepositTender.id) AS 'count'",
|
||||
"SUM(LedgerEntry.amount) AS 'total'"),
|
||||
//'conditions' => array(array('DepositTender.deposit_transaction_id' => $id)),
|
||||
'conditions' => array(array('Transaction.id' => $id)),
|
||||
'group' => 'TenderType.id',
|
||||
));
|
||||
|
||||
if (empty($result)) {
|
||||
die();
|
||||
$this->Session->setFlash(__('Invalid Deposit.', true));
|
||||
$this->redirect(array('action'=>'deposit'));
|
||||
}
|
||||
|
||||
// Add the summary to our deposit slip data container
|
||||
foreach ($result AS $type) {
|
||||
$deposit['types'][$type['TenderType']['id']] =
|
||||
$type['TenderType'] + $type[0];
|
||||
}
|
||||
|
||||
// For each form of tender in the deposit, get the deposit items
|
||||
/* foreach ($deposit['types'] AS $type_id => &$type) { */
|
||||
/* $type['entries'] = $this->Transaction->DepositTender->find */
|
||||
/* ('all', */
|
||||
/* array('contain' => array('Customer', 'LedgerEntry'), */
|
||||
/* 'conditions' => array(array('DepositTender.deposit_transaction_id' => $id), */
|
||||
/* array('DepositTender.tender_type_id' => $type_id)), */
|
||||
/* )); */
|
||||
/* } */
|
||||
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'Operations', 'header' => true);
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'View Transaction', 'url' => array('action' => 'view', $id));
|
||||
|
||||
$title = 'Deposit Slip';
|
||||
$this->set(compact('title', 'deposit'));
|
||||
$this->render('deposit_slip');
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,77 +30,63 @@ class UnitsController extends AppController {
|
||||
*/
|
||||
|
||||
function index() { $this->all(); }
|
||||
function unavailable() { $this->jqGridView('Unavailable Units'); }
|
||||
function vacant() { $this->jqGridView('Vacant Units'); }
|
||||
function occupied() { $this->jqGridView('Occupied Units'); }
|
||||
function all() { $this->jqGridView('All Units', 'all'); }
|
||||
function unavailable() { $this->gridView('Unavailable Units'); }
|
||||
function vacant() { $this->gridView('Vacant Units'); }
|
||||
function occupied() { $this->gridView('Occupied Units'); }
|
||||
function all() { $this->gridView('All Units', 'all'); }
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* virtuals: jqGridData
|
||||
* - With the application controller handling the jqGridData action,
|
||||
* virtuals: gridData
|
||||
* - With the application controller handling the gridData action,
|
||||
* these virtual functions ensure that the correct data is passed
|
||||
* to jqGrid.
|
||||
*/
|
||||
|
||||
function jqGridDataSetup(&$params) {
|
||||
parent::jqGridDataSetup($params);
|
||||
function gridDataSetup(&$params) {
|
||||
parent::gridDataSetup($params);
|
||||
if (!isset($params['action']))
|
||||
$params['action'] = 'all';
|
||||
}
|
||||
|
||||
function jqGridDataCountTables(&$params, &$model) {
|
||||
$link = array
|
||||
('link' =>
|
||||
array(// Models
|
||||
'UnitSize' => array('fields' => array('id', 'name')),
|
||||
),
|
||||
);
|
||||
function gridDataCountTables(&$params, &$model) {
|
||||
return array
|
||||
('link' => array('UnitSize' => array('fields' => array('id', 'name')),
|
||||
'CurrentLease' => array('fields' => array('id'))));
|
||||
|
||||
if ($params['action'] === 'occupied')
|
||||
$link['Lease'] = array('fields' => array(),
|
||||
// Models
|
||||
'Contact' => array('fields' => array('display_name'),
|
||||
//'type' => 'LEFT',
|
||||
),
|
||||
);
|
||||
/* if ($params['action'] === 'occupied') */
|
||||
/* $link['Lease'] = array('fields' => array(), */
|
||||
/* // Models */
|
||||
/* 'Contact' => array('fields' => array('display_name'), */
|
||||
/* //'type' => 'LEFT', */
|
||||
/* ), */
|
||||
/* ); */
|
||||
|
||||
}
|
||||
|
||||
function gridDataTables(&$params, &$model) {
|
||||
$link = $this->gridDataCountTables($params, $model);
|
||||
$link['link']['CurrentLease']['StatementEntry'] = array('fields' => array());
|
||||
return $link;
|
||||
}
|
||||
|
||||
function jqGridDataTables(&$params, &$model) {
|
||||
$link = $this->jqGridDataCountTables($params, $model);
|
||||
$link['link']['CurrentLease']['LedgerEntry'] = array('fields' => array());
|
||||
$link['link']['CurrentLease']['LedgerEntry']['Ledger'] = array('fields' => array());
|
||||
$link['link']['CurrentLease']['LedgerEntry']['Ledger']['Account'] = array('fields' => array());
|
||||
// INNER JOIN would be great, as it would ensure we're only looking
|
||||
// at the ledger entries that we truly want. However, this also
|
||||
// removes from the query any leases that do not yet have a ledger
|
||||
// entry in A/R. A solution would be to INNER JOIN these tables,
|
||||
// and LEFT JOIN it to the rest. Grouping of JOINs, however, is
|
||||
// implemented with the 'joins' tag, and is not available through
|
||||
// the Linkable behavior interface.
|
||||
//$link['link']['CurrentLease']['LedgerEntry']['Ledger']['Account']['type'] = 'INNER';
|
||||
$link['link']['CurrentLease']['LedgerEntry']['Ledger']['Account']['conditions']
|
||||
= array('Account.id' =>
|
||||
$this->Unit->CurrentLease->LedgerEntry->Ledger->Account->accountReceivableAccountID());
|
||||
return $link;
|
||||
/* function gridDataTables(&$params, &$model) { */
|
||||
/* return array */
|
||||
/* ('link' => array('Unit' => array('fields' => array('Unit.id', 'Unit.name')), */
|
||||
/* 'Customer' => array('fields' => array('Customer.id', 'Customer.name')))); */
|
||||
/* } */
|
||||
|
||||
function gridDataFields(&$params, &$model) {
|
||||
$fields = parent::gridDataFields($params, $model);
|
||||
|
||||
return array_merge($fields,
|
||||
$this->Unit->Lease->StatementEntry->chargePaymentFields(true));
|
||||
}
|
||||
|
||||
function jqGridDataFields(&$params, &$model) {
|
||||
$db = &$model->getDataSource();
|
||||
$fields = $db->fields($model, $model->alias);
|
||||
$fields[] = ("SUM(IF(Account.id IS NULL, 0," .
|
||||
" IF(LedgerEntry.debit_ledger_id = Account.id," .
|
||||
" 1, -1))" .
|
||||
" * LedgerEntry.amount) AS 'balance'");
|
||||
return $fields;
|
||||
}
|
||||
|
||||
function jqGridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::jqGridDataConditions($params, $model);
|
||||
function gridDataConditions(&$params, &$model) {
|
||||
$conditions = parent::gridDataConditions($params, $model);
|
||||
|
||||
if ($params['action'] === 'unavailable') {
|
||||
$conditions[] = $this->Unit->conditionUnavailable();
|
||||
@@ -118,27 +104,27 @@ class UnitsController extends AppController {
|
||||
return $conditions;
|
||||
}
|
||||
|
||||
function jqGridDataOrder(&$params, &$model, $index, $direction) {
|
||||
function gridDataOrder(&$params, &$model, $index, $direction) {
|
||||
// Instead of sorting by name, sort by defined order
|
||||
if ($index === 'Unit.name')
|
||||
$index = 'Unit.sort_order';
|
||||
|
||||
$order = array();
|
||||
$order[] = parent::jqGridDataOrder($params, $model, $index, $direction);
|
||||
$order[] = parent::gridDataOrder($params, $model, $index, $direction);
|
||||
|
||||
// If sorting by anything other than name (defined order)
|
||||
// add the sort-order as a secondary condition
|
||||
if ($index !== 'Unit.name')
|
||||
$order[] = parent::jqGridDataOrder($params, $model,
|
||||
$order[] = parent::gridDataOrder($params, $model,
|
||||
'Unit.sort_order', $direction);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
function jqGridRecordLinks(&$params, &$model, &$records, $links) {
|
||||
function gridDataPostProcessLinks(&$params, &$model, &$records, $links) {
|
||||
$links['Unit'] = array('name');
|
||||
$links['UnitSize'] = array('name');
|
||||
return parent::jqGridRecordLinks($params, $model, $records, $links);
|
||||
return parent::gridDataPostProcessLinks($params, $model, $records, $links);
|
||||
}
|
||||
|
||||
|
||||
@@ -242,8 +228,8 @@ class UnitsController extends AppController {
|
||||
$stats['CurrentLease']['balance'];
|
||||
|
||||
// Figure out the total security deposit for the current lease.
|
||||
$deposits = $this->Unit->Lease->findSecurityDeposits($unit['CurrentLease']['id']);
|
||||
$outstanding_deposit = $deposits['summary']['balance'];
|
||||
$deposits = $this->Unit->Lease->securityDeposits($unit['CurrentLease']['id']);
|
||||
$outstanding_deposit = $this->Unit->Lease->securityDepositBalance($unit['CurrentLease']['id']);
|
||||
}
|
||||
|
||||
// Set up dynamic menu items
|
||||
|
||||
@@ -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',
|
||||
@@ -16,13 +9,14 @@ class Account extends AppModel {
|
||||
// engine specific code. However, it doesn't
|
||||
// work with the Linkable behavior. I need to
|
||||
// look into that, just not right now.
|
||||
//'conditions' => array('CurrentLedger.close_id' => null),
|
||||
'conditions' => array('CurrentLedger.close_id IS NULL'),
|
||||
//'conditions' => array(array('CurrentLedger.close_transaction_id' => null)),
|
||||
'conditions' => array('CurrentLedger.close_transaction_id IS NULL'),
|
||||
),
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'Ledger',
|
||||
'LedgerEntry',
|
||||
);
|
||||
|
||||
|
||||
@@ -78,7 +72,7 @@ class Account extends AppModel {
|
||||
else
|
||||
$fund = $this->fundamentalType($id_or_type);
|
||||
|
||||
if ($fund == 'debit')
|
||||
if (strtolower($fund) == 'debit')
|
||||
return 'credit';
|
||||
|
||||
return 'debit';
|
||||
@@ -104,6 +98,22 @@ class Account extends AppModel {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: debitCreditFields
|
||||
* - Returns the fields necessary to determine whether the queried
|
||||
* entries are a debit, or a credit, and also the effect each have
|
||||
* on the overall balance of the account.
|
||||
*/
|
||||
|
||||
function debitCreditFields($sum = false, $balance = true,
|
||||
$entry_name = 'LedgerEntry', $account_name = 'Account') {
|
||||
return $this->LedgerEntry->debitCreditFields
|
||||
($sum, $balance, $entry_name, $account_name);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -157,60 +167,45 @@ class Account extends AppModel {
|
||||
|
||||
function relatedAccounts($attribute, $extra = null) {
|
||||
$this->cacheQueries = true;
|
||||
$account = $this->find('all', array
|
||||
('contain' => array('CurrentLedger'),
|
||||
'fields' => array('Account.id', 'Account.type', 'Account.name', 'CurrentLedger.id'),
|
||||
'conditions' => array('Account.'.$attribute => true),
|
||||
'order' => array('Account.name'),
|
||||
) + (isset($extra) ? $extra : array())
|
||||
);
|
||||
$accounts = $this->find('all', array
|
||||
('contain' => array('CurrentLedger'),
|
||||
'fields' => array('Account.id', 'Account.type', 'Account.name', 'CurrentLedger.id'),
|
||||
'conditions' => array('Account.'.$attribute => true),
|
||||
'order' => array('Account.name'),
|
||||
) + (isset($extra) ? $extra : array())
|
||||
);
|
||||
$this->cacheQueries = false;
|
||||
|
||||
return $account;
|
||||
// Rearrange to be of the form (id => name)
|
||||
$rel_accounts = array();
|
||||
foreach ($accounts AS $acct) {
|
||||
$rel_accounts[$acct['Account']['id']] = $acct['Account']['name'];
|
||||
}
|
||||
|
||||
return $rel_accounts;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: chargeAccounts
|
||||
* - Returns an array of accounts suitable for charges
|
||||
* function: xxxAccounts
|
||||
* - Returns an array of accounts suitable for activity xxx
|
||||
*/
|
||||
|
||||
function chargeAccounts() {
|
||||
// Get all accounts that support charges
|
||||
$accounts = $this->relatedAccounts('chargeable', array('order' => 'name'));
|
||||
|
||||
// Rearrange to be of the form (id => name)
|
||||
$charge_accounts = array();
|
||||
foreach ($accounts AS $acct) {
|
||||
$charge_accounts[$acct['Account']['id']] = $acct['Account']['name'];
|
||||
}
|
||||
|
||||
return $charge_accounts;
|
||||
return $this->relatedAccounts('charges', array('order' => 'name'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: paymentAccounts
|
||||
* - Returns an array of accounts suitable for payments
|
||||
*/
|
||||
|
||||
function paymentAccounts() {
|
||||
// Get all accounts that support payments
|
||||
$accounts = $this->relatedAccounts('payable', array('order' => 'name'));
|
||||
|
||||
// Rearrange to be of the form (id => name)
|
||||
$payment_accounts = array();
|
||||
foreach ($accounts AS $acct) {
|
||||
$payment_accounts[$acct['Account']['id']] = $acct['Account']['name'];
|
||||
}
|
||||
|
||||
return $payment_accounts;
|
||||
return $this->relatedAccounts('payments', array('order' => 'name'));
|
||||
}
|
||||
|
||||
function depositAccounts() {
|
||||
return $this->relatedAccounts('deposits', array('order' => 'name'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -294,513 +289,37 @@ class Account extends AppModel {
|
||||
* - Closes the current account ledger, and opens a new one
|
||||
* with the old balance carried forward.
|
||||
*/
|
||||
function closeCurrentLedger($id = null, $close_id = null) {
|
||||
$contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id')));
|
||||
|
||||
if (!$close_id) {
|
||||
$close = new Close();
|
||||
$close->create();
|
||||
if (!$close->save(array('stamp' => null), false)) {
|
||||
return false;
|
||||
}
|
||||
$close_id = $close->id;
|
||||
}
|
||||
function closeCurrentLedgers($ids = null) {
|
||||
|
||||
$this->cacheQueries = true;
|
||||
$account = $this->find('all', array
|
||||
('contain' => $contain,
|
||||
('contain' => array('CurrentLedger.id'),
|
||||
'fields' => array(),
|
||||
'conditions' =>
|
||||
$id ? array(array('Account.id' => $id)) : array()
|
||||
'conditions' => (empty($ids)
|
||||
? array()
|
||||
: array(array('Account.id' => $ids)))
|
||||
));
|
||||
$this->cacheQueries = false;
|
||||
//pr(compact('id', 'account'));
|
||||
|
||||
foreach ($account AS $acct) {
|
||||
if (!$this->Ledger->closeLedger($acct['CurrentLedger']['id'], $close_id))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
$ledger_ids = array();
|
||||
foreach ($account AS $acct)
|
||||
$ledger_ids[] = $acct['CurrentLedger']['id'];
|
||||
|
||||
return $this->Ledger->closeLedgers($ledger_ids);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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) {
|
||||
/* 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);
|
||||
}
|
||||
|
||||
$stats = $this->stats($id, $all, $cond);
|
||||
$entries = array('Entries' => $entries,
|
||||
'summary' => $stats['Ledger']);
|
||||
|
||||
/* pr(array('function' => 'Account::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'all', 'cond', 'link'), */
|
||||
/* 'vars' => compact('stats'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findLedgerEntriesRelatedToAccount
|
||||
* - Returns an array of ledger entries that belong to the given
|
||||
* account, and are related to a specific account, either just from
|
||||
* the current ledger, or from all ledgers.
|
||||
*/
|
||||
function findLedgerEntriesRelatedToAccount($id, $rel_ids, $all = false, $cond = null, $link = null) {
|
||||
/* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */
|
||||
/* 'args' => compact('id', 'rel_ids', 'all', 'cond', 'link'), */
|
||||
/* )); */
|
||||
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
if (!is_array($rel_ids))
|
||||
$rel_ids = array($rel_ids);
|
||||
|
||||
$ledger_ids = array();
|
||||
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));
|
||||
$entries = $this->findLedgerEntries($id, $all, $cond, $link);
|
||||
|
||||
/* pr(array('function' => 'Account::findLedgerEntriesRelatedToAccount', */
|
||||
/* 'args' => compact('id', 'relid', 'all', 'cond', 'link'), */
|
||||
/* 'vars' => compact('ledger_ids'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
return $entries;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findUnreconciledLedgerEntries
|
||||
* - Returns ledger entries that are not yet reconciled
|
||||
* (such as charges not paid).
|
||||
*/
|
||||
|
||||
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null, $cond = null) {
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
$cond[] = array('Account.id' => $id);
|
||||
|
||||
foreach (($fundamental_type
|
||||
? array($fundamental_type)
|
||||
: array('debit', 'credit')) AS $fund) {
|
||||
$ucfund = ucfirst($fund);
|
||||
$unreconciled[$fund]['entry'] = $this->find
|
||||
('all', array
|
||||
('link' => array
|
||||
('Ledger' => array
|
||||
('fields' => array(),
|
||||
"LedgerEntry" => array
|
||||
('class' => "{$ucfund}LedgerEntry",
|
||||
'fields' => array('id', 'customer_id', 'lease_id', 'amount'),
|
||||
"ReconciliationLedgerEntry" => array
|
||||
('class' => "{$ucfund}ReconciliationLedgerEntry",
|
||||
'fields' => array
|
||||
("COALESCE(SUM(Reconciliation.amount),0) AS 'reconciled'",
|
||||
"LedgerEntry.amount - COALESCE(SUM(Reconciliation.amount),0) AS 'balance'",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'group' => ("LedgerEntry.id" .
|
||||
" HAVING LedgerEntry.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[0]);
|
||||
$balance += $entry['balance'];
|
||||
}
|
||||
$unreconciled[$fund]['balance'] = $balance;
|
||||
}
|
||||
|
||||
return $unreconciled;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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, $cond = null) {
|
||||
$ofund = $this->fundamentalOpposite($fundamental_type);
|
||||
$unreconciled = array($ofund => array('entry'=>array(), 'balance'=>0));
|
||||
$applied = 0;
|
||||
|
||||
// if there is no money in the entry, it can reconcile nothing
|
||||
// don't bother wasting time sifting ledger entries.
|
||||
if ($amount > 0) {
|
||||
$unreconciled = $this->findUnreconciledLedgerEntries($id, $ofund, $cond);
|
||||
|
||||
foreach ($unreconciled[$ofund]['entry'] AS $i => &$entry) {
|
||||
// Determine if amount is sufficient to cover the entry
|
||||
if ($amount > $entry['balance'])
|
||||
$apply = $entry['balance'];
|
||||
elseif ($amount > 0)
|
||||
$apply = $amount;
|
||||
else {
|
||||
unset($unreconciled[$ofund]['entry'][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry['applied'] = $apply;
|
||||
$entry['reconciled'] += $apply;
|
||||
$entry['balance'] -= $apply;
|
||||
$applied += $apply;
|
||||
$amount -= $apply;
|
||||
}
|
||||
}
|
||||
|
||||
$unreconciled[$ofund]['unapplied'] = $amount;
|
||||
$unreconciled[$ofund]['applied'] = $applied;
|
||||
$unreconciled[$ofund]['balance'] -= $applied;
|
||||
return $unreconciled;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: postLedgerEntry
|
||||
* -
|
||||
* transaction_data
|
||||
* - transaction_id (optional... if set all else is ignored)
|
||||
* - Transaction
|
||||
* - stamp (optional... otherwise NOW is used)
|
||||
* - comment
|
||||
*
|
||||
* monetary_source_data
|
||||
* - monetary_source_id (optional... if set all else is ignored)
|
||||
* - account_name
|
||||
* - MonetarySource
|
||||
* - name
|
||||
*/
|
||||
|
||||
function postLedgerEntry($transaction_data,
|
||||
$monetary_data,
|
||||
$entry_data,
|
||||
$reconcile = null) {
|
||||
//pr(compact('transaction_data', 'monetary_data', 'entry_data', 'reconcile'));
|
||||
|
||||
// Automatically figure out the customer if we have the lease
|
||||
if (isset($entry_data['lease_id']) && !isset($entry_data['customer_id'])) {
|
||||
$L = new Lease();
|
||||
$L->recursive = -1;
|
||||
$lease = $L->read(null, $entry_data['lease_id']);
|
||||
$entry_data['customer_id'] = $lease['Lease']['customer_id'];
|
||||
}
|
||||
|
||||
if (!isset($entry_data['lease_id']))
|
||||
$entry_data['lease_id'] = null;
|
||||
|
||||
if (!isset($entry_data['customer_id']))
|
||||
$entry_data['customer_id'] = null;
|
||||
|
||||
// Get the Transaction squared away
|
||||
if (isset($transaction_data['transaction_id'])) {
|
||||
$transaction_data
|
||||
= array_intersect_key($transaction_data,
|
||||
array('transaction_id'=>1,
|
||||
'split_transaction_id'=>1));
|
||||
}
|
||||
elseif (isset($transaction_data['Transaction'])) {
|
||||
$transaction_data
|
||||
= array_intersect_key($transaction_data,
|
||||
array('Transaction'=>1,
|
||||
'split_transaction_id'=>1));
|
||||
}
|
||||
else {
|
||||
$transaction_data = array('Transaction'=>array('stamp' => null));
|
||||
}
|
||||
|
||||
|
||||
// Get the Monetary Source squared away
|
||||
if (isset($monetary_data)) {
|
||||
if (!isset($monetary_data['monetary_source_id'])) {
|
||||
|
||||
// Convert Account ID to name or vice versa
|
||||
if (isset($monetary_data['account_id'])) {
|
||||
$monetary_data['account_name'] = $this->name($monetary_data['account_id']);
|
||||
} elseif (isset($monetary_data['account_name'])) {
|
||||
$monetary_data['account_id'] = $this->nameToID($monetary_data['account_name']);
|
||||
}
|
||||
|
||||
if ($monetary_data['account_id'] == $this->cashAccountID()) {
|
||||
// No distinguishing features of Cash, just
|
||||
// use the shared monetary source
|
||||
$monetary_data['monetary_source_id'] =
|
||||
$this->Ledger->LedgerEntry->MonetarySource->nameToID('Cash');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($monetary_data['monetary_source_id'])) {
|
||||
$monetary_data
|
||||
= array_intersect_key($monetary_data,
|
||||
array('monetary_source_id'=>1));
|
||||
}
|
||||
else {
|
||||
// The monetary source needs to be unique
|
||||
// Create a new one dedicated to this entry
|
||||
// Give it a fancy name based on the check number
|
||||
$monetary_data['MonetarySource']['name'] = $monetary_data['account_name'];
|
||||
if ($monetary_data['account_name'] === $this->name($this->checkAccountID()) ||
|
||||
$monetary_data['account_name'] === $this->name($this->moneyOrderAccountID())) {
|
||||
$monetary_data['MonetarySource']['name'] .=
|
||||
' #' . $monetary_data['MonetarySource']['data1'];
|
||||
}
|
||||
|
||||
$monetary_data
|
||||
= array_intersect_key($monetary_data,
|
||||
array('MonetarySource'=>1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$monetary_data = array();
|
||||
}
|
||||
|
||||
// Make sure to clean out any unwanted data from the entry
|
||||
$entry_data
|
||||
= array_diff_key($entry_data,
|
||||
array('transaction_id'=>1, 'Transaction'=>1,
|
||||
'monetary_source_id'=>1, 'MonetarySource'=>1));
|
||||
|
||||
// Then add in the transaction and monetary source data
|
||||
//pr(compact('transaction_data', 'monetary_data', 'entry_data'));
|
||||
if (isset($transaction_data))
|
||||
$entry_data += $transaction_data;
|
||||
if (isset($monetary_data))
|
||||
$entry_data += $monetary_data;
|
||||
|
||||
// Set up the debit ledger id
|
||||
if (!isset($entry_data['debit_ledger_id'])) {
|
||||
$entry_data['debit_ledger_id'] =
|
||||
(isset($entry_data['debit_account_id'])
|
||||
? $this->currentLedgerID($entry_data['debit_account_id'])
|
||||
: (isset($entry_data['debit_account_name'])
|
||||
? $this->currentLedgerID($this->nameToID($entry_data['debit_account_name']))
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Set up the credit ledger id
|
||||
if (!isset($entry_data['credit_ledger_id'])) {
|
||||
$entry_data['credit_ledger_id'] =
|
||||
(isset($entry_data['credit_account_id'])
|
||||
? $this->currentLedgerID($entry_data['credit_account_id'])
|
||||
: (isset($entry_data['credit_account_name'])
|
||||
? $this->currentLedgerID($this->nameToID($entry_data['credit_account_name']))
|
||||
: null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//pr(array('pre-save', compact('entry_data')));
|
||||
// Create it!
|
||||
$new_entry = new LedgerEntry();
|
||||
$new_entry->create();
|
||||
if (!$new_entry->saveAll($entry_data, array('validate'=>false))) {
|
||||
return array('error' => true);
|
||||
}
|
||||
|
||||
// See if the user has entered some sort of non-array
|
||||
// for the reconcile parameter.
|
||||
if (isset($reconcile) && is_bool($reconcile) && $reconcile) {
|
||||
$reconcile = array('debit' => true, 'credit' => true);
|
||||
}
|
||||
elseif (isset($reconcile) && $reconcile == 'invoice') {
|
||||
$reconcile = array('credit' => 'invoice');
|
||||
}
|
||||
elseif (isset($reconcile) && $reconcile == 'receipt') {
|
||||
$reconcile = array('debit' => 'receipt');
|
||||
}
|
||||
elseif (!isset($reconcile) || !is_array($reconcile)) {
|
||||
$reconcile = array();
|
||||
}
|
||||
|
||||
// Reconcile the new entry... assume we'll have success
|
||||
$err = false;
|
||||
foreach (array_intersect_key($reconcile, array('credit'=>1,'debit'=>1))
|
||||
AS $dc_type => $reconcile_set) {
|
||||
if (!isset($reconcile_set) || (is_bool($reconcile_set) && !$reconcile_set))
|
||||
continue;
|
||||
|
||||
if ($reconcile_set === 'receipt') {
|
||||
$C = new Customer();
|
||||
$reconciled = $C->reconcileNewLedgerEntry($entry_data['customer_id'],
|
||||
$this->fundamentalOpposite($dc_type),
|
||||
$entry_data['amount']);
|
||||
|
||||
/* pr(array("reconcile receipt", */
|
||||
/* compact('reconciled', 'split_transaction', 'transaction_data'))); */
|
||||
$split_transaction = array_intersect_key($transaction_data,
|
||||
array('Transaction'=>1,
|
||||
'split_transaction_id'=>1));
|
||||
|
||||
if (isset($split_transaction['split_transaction_id']))
|
||||
$split_transaction['transaction_id'] = $split_transaction['split_transaction_id'];
|
||||
|
||||
if (is_array($reconciled) && count($reconciled[$dc_type]['entry'])) {
|
||||
foreach ($reconciled[$dc_type]['entry'] AS $rec) {
|
||||
//pr(compact('rec', 'split_transaction'));
|
||||
if (!$rec['applied'])
|
||||
continue;
|
||||
|
||||
// Create an entry to handle the splitting of the funds ("Payment")
|
||||
// and reconcile against the new cash/check/etc entry created above,
|
||||
// as well as the A/R account.
|
||||
|
||||
// Payment must debit the Receipt ledger, and credit the A/R ledger
|
||||
// debit: Receipt credit: A/R
|
||||
$ids = $this->postLedgerEntry
|
||||
($split_transaction,
|
||||
null,
|
||||
array('debit_ledger_id' => $this->currentLedgerID($this->receiptAccountID()),
|
||||
'credit_ledger_id' => $this->currentLedgerID($this->accountReceivableAccountID()),
|
||||
'amount' => $rec['applied'],
|
||||
'lease_id' => $rec['lease_id'],
|
||||
'customer_id' => $rec['customer_id'],
|
||||
),
|
||||
array('debit' => array(array('LedgerEntry' => array('id' => $new_entry->id,
|
||||
'amount' => $rec['applied']))),
|
||||
'credit' => array(array('LedgerEntry' => array('id' => $rec['id'],
|
||||
'amount' => $rec['applied']))))
|
||||
);
|
||||
// Keep using the same split transaction for all reconciled entries
|
||||
$split_transaction = array_intersect_key($ids, array('transaction_id'=>1));
|
||||
//pr(compact('ids', 'split_transaction'));
|
||||
}
|
||||
|
||||
//pr("end reconciled is array");
|
||||
}
|
||||
|
||||
//pr("end reconcile receipt");
|
||||
}
|
||||
|
||||
if (is_array($reconcile_set)) {
|
||||
//pr("reconcile_set is array");
|
||||
foreach ($reconcile_set AS $reconcile_entry) {
|
||||
if (!isset($reconcile_entry['LedgerEntry']['id']))
|
||||
continue;
|
||||
|
||||
$amount = $reconcile_entry['LedgerEntry']['amount'];
|
||||
if (!$amount)
|
||||
continue;
|
||||
|
||||
if ($dc_type == 'debit') {
|
||||
$debit_ledger_entry_id = $new_entry->id;
|
||||
$credit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
|
||||
}
|
||||
else {
|
||||
$debit_ledger_entry_id = $reconcile_entry['LedgerEntry']['id'];
|
||||
$credit_ledger_entry_id = $new_entry->id;
|
||||
}
|
||||
|
||||
$R = new Reconciliation();
|
||||
$R->create();
|
||||
if (!$R->save(compact('amount',
|
||||
'debit_ledger_entry_id',
|
||||
'credit_ledger_entry_id'), false))
|
||||
$err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$new_entry->recursive = -1;
|
||||
$new_entry->read();
|
||||
//pr(array('post-save', $entry->data));
|
||||
|
||||
$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']);
|
||||
|
||||
if (isset($split_transaction['transaction_id']))
|
||||
$ret['split_transaction_id'] = $split_transaction['transaction_id'];
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: closeAndDeposit
|
||||
* - Closes the current set of ledgers, transferring
|
||||
* their balances to specified ledger.
|
||||
*/
|
||||
function closeAndDeposit($set, $deposit_account_id) {
|
||||
|
||||
$close = new Close();
|
||||
$close->create();
|
||||
if (!$close->save(array('stamp' => null, 'comment' => 'Deposit'), false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction = array();
|
||||
foreach ($set AS $ledger) {
|
||||
// REVISIT <AP>: 20090710
|
||||
// If the user said to include a ledger in the
|
||||
// set, should we really be excluding it?
|
||||
if ($ledger['total'] == 0)
|
||||
continue;
|
||||
|
||||
$ids = $this->postLedgerEntry
|
||||
($transaction,
|
||||
null,
|
||||
array('debit_account_id' => $deposit_account_id,
|
||||
'credit_ledger_id' => $ledger['id'],
|
||||
'amount' => $ledger['total']),
|
||||
// Reconcile the account for cash/check/etc,
|
||||
// which is the credit side of this entry.
|
||||
array('credit' => $ledger['entries']));
|
||||
//pr(compact('ids'));
|
||||
|
||||
if ($ids['error'])
|
||||
die("closeAndDeposit : postLedgerEntry returned error!");
|
||||
|
||||
$transaction = array_intersect_key($ids, array('transaction_id'=>1));
|
||||
|
||||
$this->Ledger->closeLedger($ledger['id'], $close->id);
|
||||
}
|
||||
function ledgerEntries($id, $all = false, $cond = null, $link = null) {
|
||||
$ledgers = $this->ledgers($id, $all);
|
||||
return $this->Ledger->ledgerEntries($ledgers, $cond, $link);
|
||||
}
|
||||
|
||||
|
||||
@@ -811,37 +330,16 @@ class Account extends AppModel {
|
||||
* - Returns summary data from the requested account.
|
||||
*/
|
||||
|
||||
function stats($id = null, $all = false, $cond = null) {
|
||||
function stats($id = null, $all = false, $query = null) {
|
||||
if (!$id)
|
||||
return null;
|
||||
|
||||
// All old, closed ledgers MUST balance to 0.
|
||||
// However, the user may want the ENTIRE running totals,
|
||||
// (not just the balance), so we may have to query all
|
||||
// ledgers, as dictated by the $all parameter.
|
||||
$this->queryInit($query);
|
||||
$query['link'] = array('Account' => $query['link']);
|
||||
|
||||
$account = $this->find('first',
|
||||
array('contain' =>
|
||||
($all
|
||||
? array('Ledger' => array
|
||||
('fields' => array('id')))
|
||||
: array('CurrentLedger' => array
|
||||
('fields' => array('id')))
|
||||
),
|
||||
'conditions' => array
|
||||
(array('Account.id' => $id))
|
||||
));
|
||||
|
||||
$stats = array();
|
||||
if ($all) {
|
||||
foreach ($account['Ledger'] AS $ledger)
|
||||
$this->statsMerge($stats['Ledger'],
|
||||
$this->Ledger->stats($ledger['id'], $cond));
|
||||
}
|
||||
else {
|
||||
$stats['Ledger'] =
|
||||
$this->Ledger->stats($account['CurrentLedger']['id'], $cond);
|
||||
}
|
||||
foreach ($this->ledgers($id, $all) AS $ledger)
|
||||
$this->statsMerge($stats['Ledger'],
|
||||
$this->Ledger->stats($ledger, $query));
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -130,277 +130,357 @@ class LinkableBehavior extends ModelBehavior {
|
||||
}
|
||||
|
||||
public function beforeFind(&$Model, $query) {
|
||||
$this->pr(10,
|
||||
array('function' => 'Linkable::beforeFind',
|
||||
'args' => array('Model->alias' => '$Model->alias') + compact('query'),
|
||||
));
|
||||
if (isset($query[$this->_key])) {
|
||||
$optionsDefaults = $this->_defaults + array('reference' =>
|
||||
array('class' => $Model->alias,
|
||||
'alias' => $Model->alias),
|
||||
$this->_key => array());
|
||||
$optionsKeys = $this->_options + array($this->_key => true);
|
||||
if (!isset($query['fields']) || $query['fields'] === true) {
|
||||
//$query['fields'] = array_keys($Model->_schema);
|
||||
$query['fields'] = $Model->getDataSource()->fields($Model);
|
||||
} elseif (!is_array($query['fields'])) {
|
||||
$query['fields'] = array($query['fields']);
|
||||
}
|
||||
$query = am(array('joins' => array()), $query, array('recursive' => -1));
|
||||
$iterators[] = $query[$this->_key];
|
||||
$cont = 0;
|
||||
do {
|
||||
$iterator = $iterators[$cont];
|
||||
$defaults = $optionsDefaults;
|
||||
if (isset($iterator['defaults'])) {
|
||||
$defaults = array_merge($defaults, $iterator['defaults']);
|
||||
unset($iterator['defaults']);
|
||||
}
|
||||
$iterations = Set::normalize($iterator);
|
||||
$this->pr(25,
|
||||
array('checkpoint' => 'Iterations',
|
||||
compact('iterations'),
|
||||
));
|
||||
foreach ($iterations as $alias => $options) {
|
||||
if (is_null($options)) {
|
||||
$options = array();
|
||||
}
|
||||
$options = am($defaults, compact('alias'), $options);
|
||||
if (empty($options['alias'])) {
|
||||
throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__));
|
||||
}
|
||||
if (!isset($query[$this->_key]))
|
||||
return $query;
|
||||
|
||||
if (empty($options['class']))
|
||||
$options['class'] = $alias;
|
||||
|
||||
if (!isset($options['conditions']))
|
||||
$options['conditions'] = array();
|
||||
elseif (!is_array($options['conditions']))
|
||||
$options['conditions'] = array($options['conditions']);
|
||||
|
||||
$this->pr(20,
|
||||
array('checkpoint' => 'Begin Model Work',
|
||||
compact('alias', 'options'),
|
||||
));
|
||||
|
||||
$modelClass = $options['class'];
|
||||
$modelAlias = $options['alias'];
|
||||
$referenceClass = $options['reference']['class'];
|
||||
$referenceAlias = $options['reference']['alias'];
|
||||
|
||||
$_Model =& ClassRegistry::init($modelClass); // the incoming model to be linked in query
|
||||
$Reference =& ClassRegistry::init($referenceClass); // the already in query model that links to $_Model
|
||||
$this->pr(12,
|
||||
array('checkpoint' => 'Aliases Established',
|
||||
'Model' => ($modelAlias .' : '. $modelClass .
|
||||
' ('. $_Model->alias .' : '. $_Model->name .')'),
|
||||
'Reference' => ($referenceAlias .' : '. $referenceClass .
|
||||
' ('. $Reference->alias .' : '. $Reference->name .')'),
|
||||
));
|
||||
|
||||
$db =& $_Model->getDataSource();
|
||||
|
||||
$associatedThroughReference = 0;
|
||||
$association = null;
|
||||
|
||||
// Figure out how these two models are related, creating
|
||||
// a relationship if one doesn't otherwise already exists.
|
||||
if (($associations = $Reference->getAssociated()) &&
|
||||
isset($associations[$_Model->alias])) {
|
||||
$this->pr(12, array('checkpoint' => "Reference defines association to _Model"));
|
||||
$associatedThroughReference = 1;
|
||||
$type = $associations[$_Model->alias];
|
||||
$association = $Reference->{$type}[$_Model->alias];
|
||||
}
|
||||
elseif (($associations = $_Model->getAssociated()) &&
|
||||
isset($associations[$Reference->alias])) {
|
||||
$this->pr(12, array('checkpoint' => "_Model defines association to Reference"));
|
||||
$type = $associations[$Reference->alias];
|
||||
$association = $_Model->{$type}[$Reference->alias];
|
||||
}
|
||||
else {
|
||||
// No relationship... make our best effort to create one.
|
||||
$this->pr(12, array('checkpoint' => "No assocation between _Model and Reference"));
|
||||
$type = 'belongsTo';
|
||||
$_Model->bind($Reference->alias);
|
||||
// Grab the association now, since we'll unbind in a moment.
|
||||
$association = $_Model->{$type}[$Reference->alias];
|
||||
$_Model->unbindModel(array('belongsTo' => array($Reference->alias)));
|
||||
}
|
||||
|
||||
// Determine which model holds the foreign key
|
||||
if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference) {
|
||||
$primaryAlias = $referenceAlias;
|
||||
$foreignAlias = $modelAlias;
|
||||
$primaryModel = $Reference;
|
||||
$foreignModel = $_Model;
|
||||
} else {
|
||||
$primaryAlias = $modelAlias;
|
||||
$foreignAlias = $referenceAlias;
|
||||
$primaryModel = $_Model;
|
||||
$foreignModel = $Reference;
|
||||
}
|
||||
|
||||
if ($associatedThroughReference)
|
||||
$associationAlias = $referenceAlias;
|
||||
else
|
||||
$associationAlias = $modelAlias;
|
||||
|
||||
$this->recursive_array_replace("%{MODEL_ALIAS}",
|
||||
$associationAlias,
|
||||
$association['conditions']);
|
||||
|
||||
$this->pr(15,
|
||||
array('checkpoint' => 'Models Established - Check Associations',
|
||||
'primaryModel' => $primaryAlias .' : '. $primaryModel->name,
|
||||
'foreignModel' => $foreignAlias .' : '. $foreignModel->name,
|
||||
compact('type', 'association'),
|
||||
));
|
||||
|
||||
if ($type === 'hasAndBelongsToMany') {
|
||||
if (isset($association['with']))
|
||||
$linkClass = $association['with'];
|
||||
else
|
||||
$linkClass = Inflector::classify($association['joinTable']);
|
||||
|
||||
$Link =& $_Model->{$linkClass};
|
||||
|
||||
if (isset($options['linkalias']))
|
||||
$linkAlias = $options['linkalias'];
|
||||
else
|
||||
$linkAlias = $Link->alias;
|
||||
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking HABTM',
|
||||
compact('linkClass', 'linkAlias'),
|
||||
));
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
// associations with the two tables we're trying to join.
|
||||
if (empty($modelLink) && isset($Link->belongsTo[$_Model->alias]))
|
||||
$modelLink = $Link->escapeField($Link->belongsTo[$_Model->alias]['foreignKey'], $linkAlias);
|
||||
if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias]))
|
||||
$referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey'], $linkAlias);
|
||||
|
||||
// We're running quite thin here. None of the models spell
|
||||
// out the appropriate linkages. We'll have to SWAG it.
|
||||
if (empty($modelLink))
|
||||
$modelLink = $Link->escapeField(Inflector::underscore($_Model->alias) . '_id', $linkAlias);
|
||||
if (empty($referenceLink))
|
||||
$referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id', $linkAlias);
|
||||
|
||||
// Get the primary key from the tables we're joining.
|
||||
$referenceKey = $Reference->escapeField(null, $referenceAlias);
|
||||
$modelKey = $_Model->escapeField(null, $modelAlias);
|
||||
|
||||
// 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
|
||||
// cause any problem with the overall query, should the user not
|
||||
// be concerned with whether or not the join has any results.
|
||||
// They control that with the 'type' parameter which will be at
|
||||
// the top level join.
|
||||
$options['joins'][] = array('type' => 'INNER',
|
||||
'alias' => $modelAlias,
|
||||
'conditions' => "{$modelKey} = {$modelLink}",
|
||||
'table' => $db->fullTableName($_Model, true));
|
||||
|
||||
// Now for the top level join. This will be added into the list
|
||||
// of joins down below, outside of the HABTM specific code.
|
||||
$options['class'] = $linkClass;
|
||||
$options['alias'] = $linkAlias;
|
||||
$options['table'] = $Link->getDataSource()->fullTableName($Link);
|
||||
$options['conditions'][] = "{$referenceLink} = {$referenceKey}";
|
||||
}
|
||||
elseif (isset($association['foreignKey']) && $association['foreignKey']) {
|
||||
$foreignKey = $primaryModel->escapeField($association['foreignKey'], $primaryAlias);
|
||||
$primaryKey = $foreignModel->escapeField($foreignModel->primaryKey, $foreignAlias);
|
||||
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking due to foreignKey',
|
||||
compact('foreignKey', 'primaryKey'),
|
||||
));
|
||||
|
||||
// Only differentiating to help show the logical flow.
|
||||
// Either way works and this test can be tossed out
|
||||
if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference)
|
||||
$options['conditions'][] = "{$primaryKey} = {$foreignKey}";
|
||||
else
|
||||
$options['conditions'][] = "{$foreignKey} = {$primaryKey}";
|
||||
}
|
||||
else {
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking with no logic (expecting user defined)',
|
||||
));
|
||||
|
||||
// No Foreign Key... nothing we can do.
|
||||
}
|
||||
|
||||
$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'],
|
||||
),
|
||||
));
|
||||
|
||||
if (empty($options['table'])) {
|
||||
$options['table'] = $db->fullTableName($_Model, true);
|
||||
}
|
||||
|
||||
if (!isset($options['fields']) || !is_array($options['fields']))
|
||||
$options['fields'] = $db->fields($_Model, $modelAlias);
|
||||
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'])));
|
||||
|
||||
$options[$this->_key] = am($options[$this->_key], array_diff_key($options, $optionsKeys));
|
||||
$options = array_intersect_key($options, $optionsKeys);
|
||||
if (!empty($options[$this->_key])) {
|
||||
$iterators[] = $options[$this->_key] +
|
||||
array('defaults' =>
|
||||
array_merge($defaults,
|
||||
array('reference' =>
|
||||
array('class' => $modelClass,
|
||||
'alias' => $modelAlias))));
|
||||
}
|
||||
$query['joins'][] = array_intersect_key($options, array('type' => true, 'alias' => true, 'table' => true, 'joins' => true, 'conditions' => true));
|
||||
|
||||
$this->pr(19,
|
||||
array('checkpoint' => 'Model Join Complete',
|
||||
compact('options', 'modelClass', 'modelAlias', 'query'),
|
||||
));
|
||||
}
|
||||
++$cont;
|
||||
$notDone = isset($iterators[$cont]);
|
||||
} while ($notDone);
|
||||
if (!isset($query['fields']) || $query['fields'] === true) {
|
||||
$query['fields'] = $Model->getDataSource()->fields($Model);
|
||||
} elseif (!is_array($query['fields'])) {
|
||||
$query['fields'] = array($query['fields']);
|
||||
}
|
||||
$this->pr(20,
|
||||
array('function' => 'Linkable::beforeFind',
|
||||
'return' => compact('query'),
|
||||
$query = am(array('joins' => array()), $query, array('recursive' => -1));
|
||||
|
||||
$reference = array('class' => $Model->alias,
|
||||
'alias' => $Model->alias,
|
||||
);
|
||||
|
||||
$result = array_diff_key($query, array($this->_key => 1));
|
||||
$this->buildQuery($Model, $Model->alias, $Model->alias, $Model->findQueryType,
|
||||
$query[$this->_key], $result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
function buildQuery(&$Reference, $referenceClass, $referenceAlias, $query_type, $links, &$result) {
|
||||
if (empty($links))
|
||||
return;
|
||||
|
||||
$this->pr(10,
|
||||
array('begin' => 'Linkable::buildQuery',
|
||||
'args' => compact('referenceClass', 'referenceAlias', 'query_type', 'links'),
|
||||
));
|
||||
|
||||
//$defaults = $this->_defaults;// + array($this->_key => array());
|
||||
//$optionsKeys = $this->_options + array($this->_key => true);
|
||||
|
||||
$links = Set::normalize($links);
|
||||
$this->pr(24,
|
||||
array('checkpoint' => 'Normalized links',
|
||||
compact('links'),
|
||||
));
|
||||
foreach ($links as $alias => $options) {
|
||||
if (is_null($options)) {
|
||||
$options = array();
|
||||
}
|
||||
//$options = array_intersect_key($options, $optionsKeys);
|
||||
//$options = am($this->_defaults, compact('alias'), $options);
|
||||
|
||||
$options += compact('alias');
|
||||
$options += $this->_defaults;
|
||||
|
||||
if (empty($options['alias'])) {
|
||||
throw new InvalidArgumentException(sprintf('%s::%s must receive aliased links', get_class($this), __FUNCTION__));
|
||||
}
|
||||
|
||||
if (empty($options['class']))
|
||||
$options['class'] = $alias;
|
||||
|
||||
if (!isset($options['conditions']))
|
||||
$options['conditions'] = null;
|
||||
elseif (!is_array($options['conditions']))
|
||||
$options['conditions'] = array($options['conditions']);
|
||||
|
||||
$this->pr(20,
|
||||
array('checkpoint' => 'Begin Model Work',
|
||||
compact('referenceAlias', 'alias', 'options'),
|
||||
));
|
||||
|
||||
$modelClass = $options['class'];
|
||||
$modelAlias = $options['alias'];
|
||||
$Model =& ClassRegistry::init($modelClass); // the incoming model to be linked in query
|
||||
|
||||
$this->pr(12,
|
||||
array('checkpoint' => 'Model Established',
|
||||
'Reference' => ($referenceAlias .' : '. $referenceClass .
|
||||
' ('. $Reference->alias .' : '. $Reference->name .')'),
|
||||
'Model' => ($modelAlias .' : '. $modelClass .
|
||||
' ('. $Model->alias .' : '. $Model->name .')'),
|
||||
));
|
||||
|
||||
$db =& $Model->getDataSource();
|
||||
|
||||
$associatedThroughReference = 0;
|
||||
$association = null;
|
||||
|
||||
// Figure out how these two models are related, creating
|
||||
// a relationship if one doesn't otherwise already exists.
|
||||
if (($associations = $Reference->getAssociated()) &&
|
||||
isset($associations[$Model->alias])) {
|
||||
$this->pr(12, array('checkpoint' => "Reference ($referenceClass) defines association to Model ($modelClass)"));
|
||||
$associatedThroughReference = 1;
|
||||
$type = $associations[$Model->alias];
|
||||
$association = $Reference->{$type}[$Model->alias];
|
||||
}
|
||||
elseif (($associations = $Model->getAssociated()) &&
|
||||
isset($associations[$Reference->alias])) {
|
||||
$this->pr(12, array('checkpoint' => "Model ($modelClass) defines association to Reference ($referenceClass)"));
|
||||
$type = $associations[$Reference->alias];
|
||||
$association = $Model->{$type}[$Reference->alias];
|
||||
}
|
||||
else {
|
||||
// No relationship... make our best effort to create one.
|
||||
$this->pr(12, array('checkpoint' => "No assocation between Reference ($referenceClass) and Model ($modelClass)"));
|
||||
$type = 'belongsTo';
|
||||
$Model->bind($Reference->alias);
|
||||
// Grab the association now, since we'll unbind in a moment.
|
||||
$association = $Model->{$type}[$Reference->alias];
|
||||
$Model->unbindModel(array('belongsTo' => array($Reference->alias)));
|
||||
}
|
||||
|
||||
// Determine which model holds the foreign key
|
||||
if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference) {
|
||||
$primaryAlias = $referenceAlias;
|
||||
$foreignAlias = $modelAlias;
|
||||
$primaryModel = $Reference;
|
||||
$foreignModel = $Model;
|
||||
} else {
|
||||
$primaryAlias = $modelAlias;
|
||||
$foreignAlias = $referenceAlias;
|
||||
$primaryModel = $Model;
|
||||
$foreignModel = $Reference;
|
||||
}
|
||||
|
||||
if ($associatedThroughReference)
|
||||
$associationAlias = $referenceAlias;
|
||||
else
|
||||
$associationAlias = $modelAlias;
|
||||
|
||||
$this->pr(30,
|
||||
array('checkpoint' => 'Options/Association pre-merge',
|
||||
compact('association', 'options'),
|
||||
));
|
||||
|
||||
// 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) {
|
||||
$this->pr(31,
|
||||
array('checkpoint' => 'Options/Associations field original',
|
||||
compact('fld') +
|
||||
array("options[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'),
|
||||
"association[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'),
|
||||
)));
|
||||
|
||||
if (!isset($options[$fld]) ||
|
||||
(empty($options[$fld]) && !is_array($options[$fld])))
|
||||
unset($options[$fld]);
|
||||
elseif (!empty($options[$fld]) && !is_array($options[$fld]))
|
||||
$options[$fld] = array($options[$fld]);
|
||||
|
||||
if (!isset($association[$fld]) ||
|
||||
(empty($association[$fld]) && !is_array($association[$fld])))
|
||||
unset($association[$fld]);
|
||||
elseif (!empty($association[$fld]) && !is_array($association[$fld]))
|
||||
$association[$fld] = array($association[$fld]);
|
||||
|
||||
|
||||
$this->pr(31,
|
||||
array('checkpoint' => 'Options/Associations field normalize',
|
||||
compact('fld') +
|
||||
array("options[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'),
|
||||
"association[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'),
|
||||
)));
|
||||
|
||||
if (isset($options[$fld]) && isset($association[$fld]))
|
||||
$options[$fld] = array_merge($options[$fld],
|
||||
$association[$fld]);
|
||||
|
||||
$this->pr(31,
|
||||
array('checkpoint' => 'Options/Associations field merge complete',
|
||||
compact('fld') +
|
||||
array("options[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? $options[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $options) ? (isset($options[$fld]) ? gettype($options[$fld]) : '-null-') : '-unset-'),
|
||||
"association[$fld]" =>
|
||||
array('value' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? $association[$fld] : '-null-') : '-unset-',
|
||||
'type' => array_key_exists($fld, $association) ? (isset($association[$fld]) ? gettype($association[$fld]) : '-null-') : '-unset-'),
|
||||
)));
|
||||
}
|
||||
|
||||
$this->pr(30,
|
||||
array('checkpoint' => 'Options/Association post-merge',
|
||||
compact('association', 'options'),
|
||||
));
|
||||
|
||||
// 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,
|
||||
$options['conditions']);
|
||||
|
||||
$this->pr(15,
|
||||
array('checkpoint' => 'Models Established - Check Associations',
|
||||
'primaryModel' => $primaryAlias .' : '. $primaryModel->name,
|
||||
'foreignModel' => $foreignAlias .' : '. $foreignModel->name,
|
||||
compact('type', 'association', 'options'),
|
||||
));
|
||||
|
||||
if ($type === 'hasAndBelongsToMany') {
|
||||
if (isset($association['with']))
|
||||
$linkClass = $association['with'];
|
||||
else
|
||||
$linkClass = Inflector::classify($association['joinTable']);
|
||||
|
||||
$Link =& $Model->{$linkClass};
|
||||
|
||||
if (isset($options['linkalias']))
|
||||
$linkAlias = $options['linkalias'];
|
||||
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',
|
||||
'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[$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
|
||||
// associations with the two tables we're trying to join.
|
||||
if (empty($modelLink) && isset($Link->belongsTo[$Model->alias]))
|
||||
$modelLink = $Link->escapeField($Link->belongsTo[$Model->alias]['foreignKey'], $linkAlias);
|
||||
if (empty($referenceLink) && isset($Link->belongsTo[$Reference->alias]))
|
||||
$referenceLink = $Link->escapeField($Link->belongsTo[$Reference->alias]['foreignKey'], $linkAlias);
|
||||
|
||||
// We're running quite thin here. None of the models spell
|
||||
// out the appropriate linkages. We'll have to SWAG it.
|
||||
if (empty($modelLink))
|
||||
$modelLink = $Link->escapeField(Inflector::underscore($Model->alias) . '_id', $linkAlias);
|
||||
if (empty($referenceLink))
|
||||
$referenceLink = $Link->escapeField(Inflector::underscore($Reference->alias) . '_id', $linkAlias);
|
||||
|
||||
// Get the primary key from the tables we're joining.
|
||||
$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
|
||||
// cause any problem with the overall query, should the user not
|
||||
// be concerned with whether or not the join has any results.
|
||||
// They control that with the 'type' parameter which will be at
|
||||
// the top level join.
|
||||
$options['joins'][] = array('type' => 'INNER',
|
||||
'alias' => $modelAlias,
|
||||
'conditions' => "{$modelKey} = {$modelLink}",
|
||||
'table' => $db->fullTableName($Model, true));
|
||||
|
||||
// Now for the top level join. This will be added into the list
|
||||
// of joins down below, outside of the HABTM specific code.
|
||||
$options['class'] = $linkClass;
|
||||
$options['alias'] = $linkAlias;
|
||||
$options['table'] = $Link->getDataSource()->fullTableName($Link);
|
||||
$options['conditions'][] = "{$referenceLink} = {$referenceKey}";
|
||||
}
|
||||
elseif (isset($association['foreignKey']) && $association['foreignKey']) {
|
||||
$foreignKey = $primaryModel->escapeField($association['foreignKey'], $primaryAlias);
|
||||
$primaryKey = $foreignModel->escapeField($foreignModel->primaryKey, $foreignAlias);
|
||||
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking due to foreignKey',
|
||||
compact('foreignKey', 'primaryKey'),
|
||||
));
|
||||
|
||||
// Only differentiating to help show the logical flow.
|
||||
// Either way works and this test can be tossed out
|
||||
if (($type === 'hasMany' || $type === 'hasOne') ^ $associatedThroughReference)
|
||||
$options['conditions'][] = "{$primaryKey} = {$foreignKey}";
|
||||
else
|
||||
$options['conditions'][] = "{$foreignKey} = {$primaryKey}";
|
||||
}
|
||||
else {
|
||||
$this->pr(17,
|
||||
array('checkpoint' => 'Linking with no logic (expecting user defined)',
|
||||
));
|
||||
|
||||
// No Foreign Key... nothing we can do.
|
||||
}
|
||||
|
||||
$this->pr(19,
|
||||
array('checkpoint' => 'Conditions',
|
||||
array('options[conditions]' => $options['conditions'],
|
||||
),
|
||||
));
|
||||
|
||||
if (empty($options['table'])) {
|
||||
$options['table'] = $db->fullTableName($Model, true);
|
||||
}
|
||||
|
||||
if (!isset($options['fields']) || !is_array($options['fields']))
|
||||
$options['fields'] = $db->fields($Model, $modelAlias);
|
||||
elseif (!empty($options['fields']))
|
||||
$options['fields'] = $db->fields($Model, $modelAlias, $options['fields']);
|
||||
|
||||
// When performing a count query, fields are useless.
|
||||
// For everything else, we need to add them into the set.
|
||||
if ($query_type !== 'count')
|
||||
$result['fields'] = array_merge($result['fields'], $options['fields']);
|
||||
|
||||
$result['joins'][] = array_intersect_key($options,
|
||||
array('type' => true,
|
||||
'alias' => true,
|
||||
'table' => true,
|
||||
'joins' => true,
|
||||
'conditions' => true));
|
||||
|
||||
$sublinks = array_diff_key($options, $this->_options);
|
||||
$this->buildQuery($Model, $modelClass, $modelAlias, $query_type, $sublinks, $result);
|
||||
|
||||
$this->pr(19,
|
||||
array('checkpoint' => 'Model Join Complete',
|
||||
compact('referenceAlias', 'modelAlias', 'options', 'result'),
|
||||
));
|
||||
}
|
||||
|
||||
$this->pr(20,
|
||||
array('return' => 'Linkable::buildQuery',
|
||||
compact('referenceAlias'),
|
||||
));
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
class Close extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'Ledger',
|
||||
);
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -3,13 +3,6 @@ class Contact extends AppModel {
|
||||
|
||||
var $displayField = 'display_name';
|
||||
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'display_name' => array('notempty'),
|
||||
'id_federal' => array('ssn'),
|
||||
'id_exp' => array('date')
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'ContactsMethod',
|
||||
'ContactsCustomer',
|
||||
|
||||
@@ -19,17 +19,14 @@ class Customer extends AppModel {
|
||||
'conditions' => 'CurrentLease.close_date IS NULL',
|
||||
),
|
||||
'Lease',
|
||||
'LedgerEntry',
|
||||
'StatementEntry',
|
||||
'ContactsCustomer',
|
||||
|
||||
'Transaction',
|
||||
);
|
||||
|
||||
var $hasAndBelongsToMany = array(
|
||||
'Contact',
|
||||
'Transaction' => array(
|
||||
'joinTable' => 'ledger_entries',
|
||||
'foreignKey' => 'customer_id',
|
||||
'associationForeignKey' => 'transaction_id',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -73,74 +70,108 @@ class Customer extends AppModel {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findSecurityDeposits
|
||||
* function: securityDeposits
|
||||
* - Returns an array of security deposit entries
|
||||
*/
|
||||
function findSecurityDeposits($id, $link = null) {
|
||||
/* pr(array('function' => 'Customer::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
/* )); */
|
||||
function securityDeposits($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries
|
||||
($A->securityDepositAccountID(),
|
||||
true, array('LedgerEntry.customer_id' => $id), $link);
|
||||
if (!isset($query['link']['Customer']))
|
||||
$query['link']['Customer'] = array();
|
||||
if (!isset($query['link']['Customer']['fields']))
|
||||
$query['link']['Customer']['fields'] = array();
|
||||
|
||||
/* pr(array('function' => 'Customer::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
/* 'vars' => compact('customer'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
$query['conditions'][] = array('Customer.id' => $id);
|
||||
$query['conditions'][] = array('StatementEntry.account_id' =>
|
||||
$this->StatementEntry->LedgerEntry->Account->securityDepositAccountID());
|
||||
|
||||
return $entries;
|
||||
$set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true);
|
||||
return $this->prReturn($set);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findUnreconciledLedgerEntries
|
||||
* - Returns ledger entries that are not yet reconciled
|
||||
* (such as charges not paid).
|
||||
* function: securityDepositBalance
|
||||
* - Returns the balance of the customer security deposit(s)
|
||||
*/
|
||||
|
||||
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
|
||||
$A = new Account();
|
||||
$unreconciled = $A->findUnreconciledLedgerEntries
|
||||
($A->accountReceivableAccountID(),
|
||||
$fundamental_type,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
function securityDepositBalance($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
return $unreconciled;
|
||||
if (!isset($query['link']['Lease']))
|
||||
$query['link']['Lease'] = array();
|
||||
if (!isset($query['link']['Lease']['fields']))
|
||||
$query['link']['Lease']['fields'] = array();
|
||||
|
||||
$query['conditions'][] = array('Lease.id' => $id);
|
||||
$query['conditions'][] = array('StatementEntry.account_id' =>
|
||||
$this->StatementEntry->LedgerEntry->Account->securityDepositAccountID());
|
||||
|
||||
$stats = $this->StatementEntry->stats(null, $query);
|
||||
$balance = $stats['Charge']['reconciled'] - $stats['Payment']['reconciled'];
|
||||
return $this->prReturn($balance);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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: unreconciledCharges
|
||||
* - Returns charges have not yet been fully paid
|
||||
*/
|
||||
|
||||
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
|
||||
$A = new Account();
|
||||
$reconciled = $A->reconcileNewLedgerEntry
|
||||
($A->accountReceivableAccountID(),
|
||||
$fundamental_type,
|
||||
$amount,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
function unreconciledCharges($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
return $reconciled;
|
||||
if (!isset($query['link']['Customer']))
|
||||
$query['link']['Customer'] = array();
|
||||
if (!isset($query['link']['Customer']['fields']))
|
||||
$query['link']['Customer']['fields'] = array();
|
||||
/* if (!isset($query['link']['StatementEntry'])) */
|
||||
/* $query['link']['StatementEntry'] = array(); */
|
||||
/* if (!isset($query['link']['StatementEntry']['Customer'])) */
|
||||
/* $query['link']['StatementEntry']['Customer'] = array(); */
|
||||
/* if (!isset($query['link']['StatementEntry']['Customer']['fields'])) */
|
||||
/* $query['link']['StatementEntry']['Customer']['fields'] = array(); */
|
||||
|
||||
$query['conditions'][] = array('Customer.id' => $id);
|
||||
|
||||
$set = $this->StatementEntry->reconciledSet('CHARGE', $query, true);
|
||||
return $this->prReturn($set);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: excessPayments
|
||||
* - Returns payments which have not yet been fully utilized
|
||||
*/
|
||||
|
||||
function excessPayments($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
if (!isset($query['link']['StatementEntry']))
|
||||
$query['link']['StatementEntry'] = array();
|
||||
/* if (!isset($query['link']['StatementEntry']['Customer'])) */
|
||||
/* $query['link']['StatementEntry']['Customer'] = array(); */
|
||||
/* if (!isset($query['link']['StatementEntry']['Customer']['fields'])) */
|
||||
/* $query['link']['StatementEntry']['Customer']['fields'] = array(); */
|
||||
|
||||
$query['conditions'][] = array('Customer.id' => $id);
|
||||
|
||||
$set = $this->StatementEntry->reconciledSet('PAYMENT', $query, true);
|
||||
return $this->prReturn($set);
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +183,8 @@ class Customer extends AppModel {
|
||||
*/
|
||||
|
||||
function details($id = null) {
|
||||
$this->prEnter(compact('id'));
|
||||
|
||||
// Query the DB for need information.
|
||||
$customer = $this->find
|
||||
('first', array
|
||||
@@ -179,9 +212,9 @@ class Customer extends AppModel {
|
||||
$customer['stats'] = $this->stats($id);
|
||||
|
||||
// Figure out the total security deposit for the current lease.
|
||||
$customer['deposits'] = $this->findSecurityDeposits($id);
|
||||
$customer['deposits'] = $this->securityDeposits($id);
|
||||
|
||||
return $customer;
|
||||
return $this->prReturn($customer);
|
||||
}
|
||||
|
||||
|
||||
@@ -268,17 +301,62 @@ class Customer extends AppModel {
|
||||
* - Returns summary data from the requested customer.
|
||||
*/
|
||||
|
||||
function stats($id = null) {
|
||||
function stats($id = null, $query = null) {
|
||||
//$this->prFunctionLevel(20);
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
if (!$id)
|
||||
return null;
|
||||
return $this->prExit(null);
|
||||
|
||||
$A = new Account();
|
||||
$stats = $A->stats($A->accountReceivableAccountID(), true,
|
||||
array('LedgerEntry.customer_id' => $id));
|
||||
$this->queryInit($query);
|
||||
|
||||
// Pull to the top level and return
|
||||
$stats = $stats['Ledger'];
|
||||
return $stats;
|
||||
// REVISIT <AP>: 20090725
|
||||
// We'll need to go directly to the statement entries if
|
||||
// transactions are not always associated with the customer.
|
||||
// This could happen if either we remove the customer_id
|
||||
// field from Transaction, or we allow multiple customers
|
||||
// to be part of the same transaction (essentially making
|
||||
// the Transaction.customer_id meaningless).
|
||||
|
||||
/* $stats = $this->StatementEntry->find */
|
||||
/* ('first', array */
|
||||
/* ('contain' => false, */
|
||||
/* 'fields' => $this->StatementEntry->chargePaymentFields(true), */
|
||||
/* 'conditions' => array('StatementEntry.customer_id' => $id), */
|
||||
/* )); */
|
||||
|
||||
$find_stats = $this->StatementEntry->find
|
||||
('first', array
|
||||
('contain' => false,
|
||||
'fields' => $this->StatementEntry->chargePaymentFields(true),
|
||||
'conditions' => array('StatementEntry.customer_id' => $id),
|
||||
));
|
||||
$find_stats = $find_stats[0];
|
||||
$this->pr(17, compact('find_stats'));
|
||||
|
||||
$tquery = $query;
|
||||
$tquery['conditions'][] = array('StatementEntry.customer_id' => $id);
|
||||
$statement_stats = $this->StatementEntry->stats(null, $tquery);
|
||||
$statement_stats['balance'] = $statement_stats['Charge']['balance'];
|
||||
$this->pr(17, compact('statement_stats'));
|
||||
|
||||
$tquery = $query;
|
||||
//$tquery['conditions'][] = array('StatementEntry.customer_id' => $id);
|
||||
$tquery['conditions'][] = array('Transaction.customer_id' => $id);
|
||||
$transaction_stats = $this->Transaction->stats(null, $tquery);
|
||||
$transaction_stats += $transaction_stats['StatementEntry'];
|
||||
$this->pr(17, compact('transaction_stats'));
|
||||
|
||||
$tquery = $query;
|
||||
//$tquery['conditions'][] = array('StatementEntry.customer_id' => $id);
|
||||
$tquery['conditions'][] = array('Transaction.customer_id' => $id);
|
||||
$ar_transaction_stats = $this->Transaction->stats(null, $tquery,
|
||||
$this->Transaction->Account->accountReceivableAccountID());
|
||||
$ar_transaction_stats += $ar_transaction_stats['LedgerEntry'];
|
||||
$this->pr(17, compact('ar_transaction_stats'));
|
||||
|
||||
//$stats = $ar_transaction_stats;
|
||||
$stats = $find_stats;
|
||||
return $this->prReturn($stats);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
96
site/models/double_entry.php
Normal file
96
site/models/double_entry.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
class DoubleEntry extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'DebitEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
),
|
||||
'CreditEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyDoubleEntry
|
||||
* - Verifies consistenty of new double entry data
|
||||
* (not in a pre-existing double entry)
|
||||
*/
|
||||
function verifyDoubleEntry($entry1, $entry2, $entry1_tender = null) {
|
||||
/* pr(array("DoubleEntry::verifyDoubleEntry()" */
|
||||
/* => compact('entry1', 'entry2', 'entry1_tender'))); */
|
||||
|
||||
$LE = new LedgerEntry();
|
||||
if (!$LE->verifyLedgerEntry($entry1, $entry1_tender)) {
|
||||
/* pr(array("DoubleEntry::verifyDoubleEntry()" */
|
||||
/* => "Entry1 verification failed")); */
|
||||
return false;
|
||||
}
|
||||
if (!$LE->verifyLedgerEntry($entry2)) {
|
||||
/* pr(array("DoubleEntry::verifyDoubleEntry()" */
|
||||
/* => "Entry2 verification failed")); */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(($entry1['crdr'] === 'DEBIT' && $entry2['crdr'] === 'CREDIT') ||
|
||||
($entry1['crdr'] === 'CREDIT' && $entry2['crdr'] === 'DEBIT')) ||
|
||||
($entry1['amount'] != $entry2['amount'])) {
|
||||
/* pr(array("DoubleEntry::verifyDoubleEntry()" */
|
||||
/* => "Double Entry verification failed")); */
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addDoubleEntry
|
||||
* - Inserts new Double Entry into the database
|
||||
*/
|
||||
|
||||
function addDoubleEntry($entry1, $entry2, $entry1_tender = null) {
|
||||
/* pr(array('DoubleEntry::addDoubleEntry' => */
|
||||
/* compact('entry1', 'entry2', 'entry1_tender'))); */
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyDoubleEntry($entry1, $entry2, $entry1_tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Since this model only relates to DebitEntry and CreditEntry...
|
||||
$LE = new LedgerEntry();
|
||||
|
||||
// Add the first ledger entry to the database
|
||||
$result = $LE->addLedgerEntry($entry1, $entry1_tender);
|
||||
$ret['Entry1'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Add the second ledger entry to the database
|
||||
$result = $LE->addLedgerEntry($entry2);
|
||||
$ret['Entry2'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
// Now link them as a double entry
|
||||
$double_entry = array();
|
||||
$double_entry['debit_entry_id'] =
|
||||
($entry1['crdr'] === 'DEBIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id'];
|
||||
$double_entry['credit_entry_id'] =
|
||||
($entry1['crdr'] === 'CREDIT') ? $ret['Entry1']['ledger_entry_id'] : $ret['Entry2']['ledger_entry_id'];
|
||||
|
||||
/* pr(array('DoubleEntry::addDoubleEntry' => */
|
||||
/* array('checkpoint' => 'Pre-Save') */
|
||||
/* + compact('double_entry'))); */
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($double_entry))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$ret['double_entry_id'] = $this->id;
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
}
|
||||
@@ -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,109 +9,124 @@ class Lease extends AppModel {
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
'StatementEntry',
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: accountId
|
||||
* - Returns the accountId of the given lease
|
||||
*/
|
||||
function accountId($id) {
|
||||
$A = new Account();
|
||||
return $A->invoiceAccountID();
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findAccountEntries
|
||||
* - Returns an array of ledger entries from the account of the given
|
||||
* lease.
|
||||
*/
|
||||
function findAccountEntries($id, $all = false, $cond = null, $link = null) {
|
||||
/* pr(array('function' => 'Lease::findAccountEntries', */
|
||||
/* 'args' => compact('id', 'all', 'cond', 'link'), */
|
||||
/* )); */
|
||||
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
$cond[] = array('LedgerEntry.lease_id' => $id);
|
||||
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries($this->accountId($id),
|
||||
$all, $cond, $link);
|
||||
|
||||
/* pr(array('function' => 'Lease::findAccountEntries', */
|
||||
/* 'args' => compact('id', 'all', 'cond', 'link'), */
|
||||
/* 'vars' => compact('lease'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
return $entries;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findSecurityDeposits
|
||||
* function: securityDeposits
|
||||
* - Returns an array of security deposit entries
|
||||
*/
|
||||
function findSecurityDeposits($id, $link = null) {
|
||||
/* pr(array('function' => 'Lease::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
/* )); */
|
||||
function securityDeposits($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
$A = new Account();
|
||||
$entries = $A->findLedgerEntries
|
||||
($A->securityDepositAccountID(),
|
||||
true, array('LedgerEntry.lease_id' => $id), $link);
|
||||
if (!isset($query['link']['Lease']))
|
||||
$query['link']['Lease'] = array();
|
||||
if (!isset($query['link']['Lease']['fields']))
|
||||
$query['link']['Lease']['fields'] = array();
|
||||
|
||||
/* pr(array('function' => 'Lease::findSecurityDeposits', */
|
||||
/* 'args' => compact('id', 'link'), */
|
||||
/* 'vars' => compact('lease'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
return $entries;
|
||||
$query['conditions'][] = array('Lease.id' => $id);
|
||||
$query['conditions'][] = array('StatementEntry.account_id' =>
|
||||
$this->StatementEntry->LedgerEntry->Account->securityDepositAccountID());
|
||||
|
||||
$set = $this->StatementEntry->reconciledSet('CHARGE', $query, false, true);
|
||||
|
||||
/* $set['summary'] = array('total' => $set['summary']['Charge']['total'], */
|
||||
/* 'balance' => $set['summary']['Charge']['reconciled'], */
|
||||
/* ); */
|
||||
|
||||
return $this->prReturn($set);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findUnreconciledLedgerEntries
|
||||
* - Returns ledger entries that are not yet reconciled
|
||||
* (such as charges not paid).
|
||||
* function: securityDepositBalance
|
||||
* - Returns the balance of the lease security deposit(s)
|
||||
*/
|
||||
|
||||
function findUnreconciledLedgerEntries($id = null, $fundamental_type = null) {
|
||||
$A = new Account();
|
||||
return $A->findUnreconciledLedgerEntries
|
||||
($this->accountId($id), $fundamental_type, array('LedgerEntry.lease_id' => $id));
|
||||
function securityDepositBalance($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$this->queryInit($query);
|
||||
|
||||
if (!isset($query['link']['Lease']))
|
||||
$query['link']['Lease'] = array();
|
||||
if (!isset($query['link']['Lease']['fields']))
|
||||
$query['link']['Lease']['fields'] = array();
|
||||
|
||||
$query['conditions'][] = array('Lease.id' => $id);
|
||||
$query['conditions'][] = array('StatementEntry.account_id' =>
|
||||
$this->StatementEntry->LedgerEntry->Account->securityDepositAccountID());
|
||||
|
||||
$stats = $this->StatementEntry->stats(null, $query);
|
||||
$balance = $stats['Charge']['reconciled'] - $stats['Payment']['reconciled'];
|
||||
return $this->prReturn($balance);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* 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: releaseSecurityDeposits
|
||||
* - Releases all security deposits associated with this lease.
|
||||
* That simply makes a payment out of them, which can be used
|
||||
* to pay outstanding customer charges, or simply to become
|
||||
* a customer surplus (customer credit).
|
||||
*/
|
||||
function releaseSecurityDeposits($id, $query = null) {
|
||||
$this->prFunctionLevel(30);
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
|
||||
function reconcileNewLedgerEntry($id, $fundamental_type, $amount) {
|
||||
$A = new Account();
|
||||
return $A->reconcileNewLedgerEntry
|
||||
($this->accountId($id), $fundamental_type, $amount, array('LedgerEntry.lease_id' => $id));
|
||||
$secdeps = $this->securityDeposits($id, $query);
|
||||
$secdeps = $secdeps['entries'];
|
||||
$this->pr(20, compact('secdeps'));
|
||||
|
||||
$this->securityDepositBalance($id, $query);
|
||||
die();
|
||||
|
||||
// If there are no paid security deposits, then
|
||||
// we can consider all security deposits released.
|
||||
if (count($secdeps) == 0)
|
||||
return $this->prReturn(true);
|
||||
|
||||
// Build a transaction
|
||||
$release = array('Transaction' => array(), 'Entry' => array());
|
||||
$release['Transaction']['comment'] = "Security Deposit Release";
|
||||
foreach ($secdeps AS $charge) {
|
||||
if ($charge['StatementEntry']['type'] !== 'CHARGE')
|
||||
die("INTERNAL ERROR: SECURITY DEPOSIT IS NOT CHARGE");
|
||||
|
||||
// Since security deposits are being released, this also means
|
||||
// we're reducing any oustanding amount on a security deposit
|
||||
// since we no longer expect it to be owed.
|
||||
// REVISIT <AP>: 20090730
|
||||
// This is kludgy, and I really don't like it. However, this
|
||||
// is not presently something that even happens at the moment,
|
||||
// so this solution will have to work until we come up with
|
||||
// something more robust, like flagging those charges as defunct.
|
||||
if ($charge['StatementEntry']['balance'] > 0) {
|
||||
$this->StatementEntry->id = $charge['StatementEntry']['id'];
|
||||
$this->StatementEntry->saveField('amount', $charge['StatementEntry']['reconciled']);
|
||||
}
|
||||
|
||||
$release['Entry'][] =
|
||||
array('amount' => $charge['StatementEntry']['reconciled'],
|
||||
'account_id' => $this->StatementEntry->LedgerEntry->Account->securityDepositAccountID(),
|
||||
'comment' => "Released Security Deposit",
|
||||
);
|
||||
}
|
||||
|
||||
$customer_id = $secdeps[0]['StatementEntry']['customer_id'];
|
||||
$lease_id = $secdeps[0]['StatementEntry']['lease_id'];
|
||||
|
||||
$result = $this->StatementEntry->Transaction->addReceipt
|
||||
($release, $customer_id, $lease_id);
|
||||
|
||||
return $this->prReturn($result);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,42 +142,35 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function rentLastCharges($id) {
|
||||
$A = new Account();
|
||||
$this->prEnter(compact('id'));
|
||||
$rent_account_id = $this->StatementEntry->LedgerEntry->Account->rentAccountID();
|
||||
$entries = $this->find
|
||||
('all',
|
||||
array('link' =>
|
||||
array(// Models
|
||||
'LedgerEntry' => array
|
||||
('Ledger' => array
|
||||
('fields' => array(),
|
||||
'Account' => array
|
||||
('fields' => array(),
|
||||
'Ledger' => array
|
||||
('alias' => 'Lx',
|
||||
'fields' => array(),
|
||||
'LedgerEntry' => array
|
||||
('alias' => 'LEx',
|
||||
'fields' => array(),
|
||||
'conditions' => array
|
||||
('LEx.effective_date = DATE_ADD(LedgerEntry.through_date, INTERVAL 1 day)',
|
||||
'LEx.lease_id = LedgerEntry.lease_id',
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'StatementEntry',
|
||||
|
||||
'SEx' =>
|
||||
array('class' => 'StatementEntry',
|
||||
'fields' => array(),
|
||||
'conditions' => array
|
||||
('SEx.effective_date = DATE_ADD(StatementEntry.through_date, INTERVAL 1 day)',
|
||||
'SEx.lease_id = StatementEntry.lease_id',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
//'fields' => array('id', 'amount', 'effective_date', 'through_date'),
|
||||
'fields' => array(),
|
||||
'conditions' => array(array('Lease.id' => $id),
|
||||
array('Account.id' => $A->rentAccountID()),
|
||||
array('LEx.id' => null),
|
||||
array('StatementEntry.type' => 'CHARGE'),
|
||||
array('StatementEntry.account_id' => $rent_account_id),
|
||||
array('SEx.id' => null),
|
||||
),
|
||||
)
|
||||
);
|
||||
return $entries;
|
||||
|
||||
return $this->prReturn($entries);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,10 +182,11 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function rentChargeGaps($id) {
|
||||
$this->prEnter(compact('id'));
|
||||
$entries = $this->rentLastCharges($id);
|
||||
if ($entries && count($entries) > 1)
|
||||
return true;
|
||||
return false;
|
||||
return $this->prReturn(true);
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,12 +202,13 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function rentChargeThrough($id) {
|
||||
$this->prEnter(compact('id'));
|
||||
$entries = $this->rentLastCharges($id);
|
||||
if (!$entries)
|
||||
return false;
|
||||
return $this->prReturn(false);
|
||||
if (count($entries) != 1)
|
||||
return null;
|
||||
return $entries[0]['LedgerEntry']['through_date'];
|
||||
return $this->prReturn(null);
|
||||
return $this->prReturn($entries[0]['StatementEntry']['through_date']);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,64 +220,56 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function rentPaidThrough($id) {
|
||||
$this->prEnter(compact('id'));
|
||||
$rent_account_id = $this->StatementEntry->LedgerEntry->Account->rentAccountID();
|
||||
|
||||
// Income / Receipt / Money
|
||||
// debit: A/R credit: Income <-- this entry
|
||||
// debit: Receipt credit: A/R <-- ReceiptLedgerEntry, below
|
||||
// debit: Money credit: Receipt <-- MoneyLedgerEntry, below
|
||||
// First, see if we can find any unpaid entries. Of course,
|
||||
// the first unpaid entry gives us a very direct indication
|
||||
// of when the customer is paid up through, which is 1 day
|
||||
// prior to the effective date of that first unpaid charge.
|
||||
$rent = $this->StatementEntry->reconciledSet
|
||||
('CHARGE',
|
||||
array('fields' =>
|
||||
array('StatementEntry.*',
|
||||
'DATE_SUB(StatementEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
|
||||
),
|
||||
|
||||
$query = array
|
||||
('link' => array
|
||||
(
|
||||
'CreditLedger' =>
|
||||
array('fields' => array(),
|
||||
'Account' =>
|
||||
array('fields' => array(),
|
||||
),
|
||||
),
|
||||
'conditions' =>
|
||||
array(array('StatementEntry.lease_id' => $id),
|
||||
array('StatementEntry.account_id' => $rent_account_id)),
|
||||
|
||||
// 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',
|
||||
'fields' => array(),
|
||||
'order' => array('StatementEntry.effective_date'),
|
||||
),
|
||||
true);
|
||||
$this->pr(20, $rent, "Unpaid rent");
|
||||
|
||||
// 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'),
|
||||
),
|
||||
),
|
||||
),
|
||||
if ($rent['entries'])
|
||||
return $this->prReturn($rent['entries'][0]['StatementEntry']['paid_through']);
|
||||
|
||||
'fields' => array('LedgerEntry.amount',
|
||||
'DATE_SUB(LedgerEntry.effective_date, INTERVAL 1 DAY) AS paid_through',
|
||||
),
|
||||
|
||||
'group' => 'LedgerEntry.id HAVING paid <> LedgerEntry.amount',
|
||||
// If we don't have any unpaid charges (great!), then the
|
||||
// customer is paid up through the last day of the last
|
||||
// charge. So, search for paid charges, which already
|
||||
// have the paid through date saved as part of the entry.
|
||||
$rent = $this->StatementEntry->reconciledSet
|
||||
('CHARGE',
|
||||
array('conditions' =>
|
||||
array(array('StatementEntry.lease_id' => $id),
|
||||
array('StatementEntry.account_id' => $rent_account_id)),
|
||||
|
||||
'conditions' => array(array('LedgerEntry.lease_id' => $id),
|
||||
array('Account.id' => $this->LedgerEntry->Ledger->Account->rentAccountID()),
|
||||
),
|
||||
'order' => array('LedgerEntry.effective_date',
|
||||
),
|
||||
);
|
||||
'order' => array('StatementEntry.through_date DESC'),
|
||||
),
|
||||
false);
|
||||
$this->pr(20, $rent, "Paid rent");
|
||||
|
||||
$rent = $this->LedgerEntry->find('first', $query);
|
||||
if ($rent)
|
||||
return $rent[0]['paid_through'];
|
||||
if ($rent['entries'])
|
||||
return $this->prReturn($rent['entries'][0]['StatementEntry']['through_date']);
|
||||
|
||||
$query['fields'] = 'LedgerEntry.through_date';
|
||||
$query['order'] = 'LedgerEntry.through_date DESC';
|
||||
$query['group'] = 'LedgerEntry.id';
|
||||
$rent = $this->LedgerEntry->find('first', $query);
|
||||
if ($rent)
|
||||
return $rent['LedgerEntry']['through_date'];
|
||||
|
||||
return null;
|
||||
// After all that, having found that there are no unpaid
|
||||
// charges, and in fact, no paid charges either, we cannot
|
||||
// possibly say when the customer is paid through.
|
||||
return $this->prReturn(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -301,7 +282,10 @@ class Lease extends AppModel {
|
||||
|
||||
function moveIn($customer_id, $unit_id,
|
||||
$deposit = null, $rent = null,
|
||||
$stamp = null, $comment = null) {
|
||||
$stamp = null, $comment = null)
|
||||
{
|
||||
$this->prEnter(compact('customer_id', 'unit_id',
|
||||
'deposit', 'rent', 'stamp', 'comment'));
|
||||
|
||||
$lt = $this->LeaseType->find('first',
|
||||
array('conditions' =>
|
||||
@@ -355,7 +339,7 @@ class Lease extends AppModel {
|
||||
'deposit' => $deposit,
|
||||
'rent' => $rent,
|
||||
'comment' => $comment), false)) {
|
||||
return null;
|
||||
return $this->prReturn(null);
|
||||
}
|
||||
|
||||
// Set the lease number to be the same as the lease ID
|
||||
@@ -372,7 +356,7 @@ class Lease extends AppModel {
|
||||
// was waived, pro-rated, etc.
|
||||
|
||||
// Return the new lease ID
|
||||
return $this->id;
|
||||
return $this->prReturn($this->id);
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +368,10 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function moveOut($id, $status = 'VACANT',
|
||||
$stamp = null, $close = false) {
|
||||
$stamp = null, $close = false)
|
||||
{
|
||||
$this->prEnter(compact('id', 'status', 'stamp', 'close'));
|
||||
|
||||
// Use NOW if not given a moveout date
|
||||
if (!isset($stamp))
|
||||
$stamp = date('Y-m-d G:i:s');
|
||||
@@ -418,8 +405,10 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function close($id, $stamp = null) {
|
||||
$this->prEnter(compact('id', 'stamp'));
|
||||
|
||||
if (!$this->closeable($id))
|
||||
return false;
|
||||
return $this->prReturn(false);
|
||||
|
||||
// Reset the data
|
||||
$this->create();
|
||||
@@ -434,7 +423,7 @@ class Lease extends AppModel {
|
||||
|
||||
// Save it!
|
||||
$this->save($this->data, false);
|
||||
return true;
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -446,27 +435,29 @@ class Lease extends AppModel {
|
||||
*/
|
||||
|
||||
function closeable($id) {
|
||||
$this->prEnter(compact('id'));
|
||||
|
||||
$this->recursive = -1;
|
||||
$this->read(null, $id);
|
||||
|
||||
// We can't close a lease that's still in use
|
||||
if (!isset($this->data['Lease']['moveout_date']))
|
||||
return false;
|
||||
return $this->prReturn(false);
|
||||
|
||||
// We can't close a lease that's already closed
|
||||
if (isset($this->data['Lease']['close_date']))
|
||||
return false;
|
||||
return $this->prReturn(false);
|
||||
|
||||
$deposits = $this->findSecurityDeposits($id);
|
||||
$deposit_balance = $this->securityDepositBalance($id);
|
||||
$stats = $this->stats($id);
|
||||
|
||||
// A lease can only be closed if there are no outstanding
|
||||
// security deposits, and if the account balance is zero.
|
||||
if ($deposits['summary']['balance'] != 0 || $stats['balance'] != 0)
|
||||
return false;
|
||||
if ($deposit_balance != 0)
|
||||
return $this->prReturn(false);
|
||||
|
||||
// Apparently this lease meets all the criteria!
|
||||
return true;
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -489,17 +480,47 @@ class Lease extends AppModel {
|
||||
* - Returns summary data from the requested lease.
|
||||
*/
|
||||
|
||||
function stats($id = null) {
|
||||
function stats($id = null, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
if (!$id)
|
||||
return null;
|
||||
return $this->prReturn(null);
|
||||
|
||||
$A = new Account();
|
||||
$stats = $A->stats($A->accountReceivableAccountID(), true,
|
||||
array('LedgerEntry.lease_id' => $id));
|
||||
$this->queryInit($query);
|
||||
|
||||
// Pull to the top level and return
|
||||
$stats = $stats['Ledger'];
|
||||
return $stats;
|
||||
//$query['link'] = array('Lease' => $query['link']);
|
||||
/* if (!isset($query['link']['StatementEntry'])) */
|
||||
/* $query['link']['StatementEntry'] = array(); */
|
||||
/* if (!isset($query['link']['StatementEntry']['ChargeEntry'])) */
|
||||
/* $query['link']['StatementEntry']['ChargeEntry'] = array(); */
|
||||
|
||||
/* $query['link']['StatementEntry']['fields'] = array(); */
|
||||
/* $query['link']['ChargeEntry']['fields'] = array(); */
|
||||
/* $query['link']['ChargeEntry']['Account']['fields'] = array(); */
|
||||
/* $query['link']['ChargeEntry']['StatementEntry']['fields'] = array(); */
|
||||
/* $query['link']['ChargeEntry']['StatementEntry']['Invoice']['fields'] = array(); */
|
||||
|
||||
if (!isset($query['fields']))
|
||||
$query['fields'] = array();
|
||||
|
||||
$query['fields'] = array_merge($query['fields'],
|
||||
$this->StatementEntry->chargePaymentFields(true));
|
||||
|
||||
$query['conditions'][] = array('StatementEntry.lease_id' => $id);
|
||||
|
||||
$query['group'] = null;
|
||||
|
||||
$stats = $this->StatementEntry->find('first', $query);
|
||||
//$this->pr(20, compact('query', 'stats'));
|
||||
|
||||
// The fields are all tucked into the [0] index,
|
||||
// and the rest of the array is useless (empty).
|
||||
$stats = $stats[0];
|
||||
|
||||
// Make sure we have a non-null balance
|
||||
if (!isset($stats['balance']))
|
||||
$stats['balance'] = 0;
|
||||
|
||||
return $this->prReturn($stats);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +1,15 @@
|
||||
<?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'),
|
||||
'Close',
|
||||
'CloseTransaction' => array('className' => 'Transaction'),
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry' => 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')),
|
||||
|
||||
// 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__$})',
|
||||
|
||||
'counterQuery' => ''
|
||||
),
|
||||
'DebitLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'foreignKey' => 'debit_ledger_id',
|
||||
'dependent' => false,
|
||||
),
|
||||
'CreditLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'foreignKey' => 'credit_ledger_id',
|
||||
'dependent' => false,
|
||||
),
|
||||
'Transaction',
|
||||
'LedgerEntry',
|
||||
);
|
||||
|
||||
|
||||
@@ -55,10 +22,11 @@ class Ledger extends AppModel {
|
||||
function accountID($id) {
|
||||
$this->cacheQueries = true;
|
||||
$item = $this->find('first', array
|
||||
('contain' => 'Account.id',
|
||||
('link' => array('Account'),
|
||||
'conditions' => array('Ledger.id' => $id),
|
||||
));
|
||||
$this->cacheQueries = false;
|
||||
//pr(compact('id', 'item'));
|
||||
return $item['Account']['id'];
|
||||
}
|
||||
|
||||
@@ -80,120 +48,86 @@ class Ledger extends AppModel {
|
||||
* function: closeLedger
|
||||
* - Closes the current ledger, and returns a fresh one
|
||||
*/
|
||||
function closeLedger($id, $close_id) {
|
||||
$this->recursive = -1;
|
||||
function closeLedgers($ids) {
|
||||
$ret = array('new_ledger_ids' => array());
|
||||
|
||||
$stamp = date('Y-m-d G:i:s');
|
||||
$this->id = $id;
|
||||
$this->read();
|
||||
$this->data['Ledger']['close_id'] = $close_id;
|
||||
$this->save($this->data, false);
|
||||
$entries = array();
|
||||
foreach ($ids AS $id) {
|
||||
// Query stats to get the balance forward
|
||||
$stats = $this->stats($id);
|
||||
|
||||
$stats = $this->stats($id);
|
||||
// Populate fields from the current ledger
|
||||
$this->recursive = -1;
|
||||
$this->id = $id;
|
||||
$this->read();
|
||||
|
||||
$this->read();
|
||||
$this->data['Ledger']['id'] = null;
|
||||
$this->data['Ledger']['close_id'] = null;
|
||||
$this->data['Ledger']['prior_ledger_id'] = $id;
|
||||
$this->data['Ledger']['comment'] = null;
|
||||
++$this->data['Ledger']['sequence'];
|
||||
$this->id = null;
|
||||
$this->save($this->data, false);
|
||||
//pr($this->data);
|
||||
// Build a new ledger to replace the current one
|
||||
$this->data['Ledger']['id'] = null;
|
||||
$this->data['Ledger']['close_transaction_id'] = null;
|
||||
$this->data['Ledger']['prior_ledger_id'] = $id;
|
||||
$this->data['Ledger']['comment'] = null;
|
||||
++$this->data['Ledger']['sequence'];
|
||||
$this->data['Ledger']['name'] =
|
||||
($this->data['Ledger']['account_id'] .
|
||||
'-' .
|
||||
$this->data['Ledger']['sequence']);
|
||||
|
||||
if ($stats['balance'] == 0)
|
||||
return $this->id;
|
||||
// Save the new ledger
|
||||
$this->id = null;
|
||||
if (!$this->save($this->data, false))
|
||||
return array('error' => true, 'new_ledger_data' => $this->data) + $ret;
|
||||
$ret['new_ledger_ids'][] = $this->id;
|
||||
|
||||
$this->read();
|
||||
$ftype = $this->Account->fundamentalType($this->data['Ledger']['account_id']);
|
||||
$otype = $this->Account->fundamentalOpposite($ftype);
|
||||
|
||||
// Create a transaction for balance transfer
|
||||
$transaction = new Transaction();
|
||||
$transaction->create();
|
||||
if (!$transaction->save(array(), false)) {
|
||||
return null;
|
||||
$entries[] = array('old_ledger_id' => $id,
|
||||
'new_ledger_id' => $this->id,
|
||||
'amount' => $stats['balance']);
|
||||
}
|
||||
|
||||
// Create an entry to carry the balance forward
|
||||
$carry_entry_data = array
|
||||
($ftype.'_ledger_id' => $this->id,
|
||||
$otype.'_ledger_id' => $id,
|
||||
'transaction_id' => $transaction->id,
|
||||
'amount' => $stats['balance'],
|
||||
'comment' => "Ledger Balance Forward",
|
||||
);
|
||||
// Perform the close
|
||||
$result = $this->Transaction->addClose(array('Transaction' => array(),
|
||||
'Ledger' => $entries));
|
||||
$ret['Transaction'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$carry_entry = new LedgerEntry();
|
||||
$carry_entry->create();
|
||||
if (!$carry_entry->save($carry_entry_data, false)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: findLedgerEntries
|
||||
* - Returns an array of ledger entries that belong to a given
|
||||
* ledger. There is extra work done... see the LedgerEntry model.
|
||||
* function: debitCreditFields
|
||||
* - Returns the fields necessary to determine whether the queried
|
||||
* entries are a debit, or a credit, and also the effect each have
|
||||
* on the overall balance of the ledger.
|
||||
*/
|
||||
function findLedgerEntries($id, $account_type = null, $cond = null, $link = null) {
|
||||
/* pr(array('function' => 'Ledger::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
|
||||
/* )); */
|
||||
function debitCreditFields($sum = false, $balance = true,
|
||||
$entry_name = 'LedgerEntry', $account_name = 'Account') {
|
||||
return $this->LedgerEntry->debitCreditFields
|
||||
($sum, $balance, $entry_name, $account_name);
|
||||
}
|
||||
|
||||
if (!isset($account_type)) {
|
||||
$ledger = $this->find('first', array
|
||||
('contain' => array
|
||||
('Account' => array
|
||||
('fields' => array('type'),
|
||||
),
|
||||
),
|
||||
'fields' => array(),
|
||||
'conditions' => array(array('Ledger.id' => $id)),
|
||||
));
|
||||
$account_type = $ledger['Account']['type'];
|
||||
}
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: ledgerEntries
|
||||
* - Returns an array of ledger entries that belong to a given
|
||||
* ledger. There is extra work done to establish debit/credit
|
||||
*/
|
||||
function ledgerEntries($ids, $query = null) {
|
||||
if (empty($ids))
|
||||
return null;
|
||||
|
||||
// If the requested entries are limited by date, we must calculate
|
||||
// a balance forward, or the resulting balance will be thrown off.
|
||||
//
|
||||
// REVISIT <AP>: This obviously is more general than date.
|
||||
// As such, it will not work (or, only work if the
|
||||
// condition only manages to exclude the first parts
|
||||
// of the ledger, nothing in the middle or at the
|
||||
// end. For now, I'll just create an 'other' entry,
|
||||
// not necessarily a balance forward.
|
||||
$entries = $this->LedgerEntry->find
|
||||
('all', array
|
||||
('link' => array('Ledger' => array('Account')),
|
||||
'fields' => array_merge(array("LedgerEntry.*"),
|
||||
$this->LedgerEntry->debitCreditFields()),
|
||||
'conditions' => array('LedgerEntry.ledger_id' => $ids),
|
||||
));
|
||||
|
||||
$bf = array();
|
||||
if (0 && isset($cond)) {
|
||||
//$date = '<NOT IMPLEMENTED>';
|
||||
$stats = $this->stats($id, array('NOT' => array($cond)));
|
||||
$bf = array(array(array('debit' => $stats['debits'],
|
||||
'credit' => $stats['credits'],
|
||||
'balance' => $stats['balance']),
|
||||
|
||||
'LedgerEntry' => array('id' => null,
|
||||
//'comment' => "Balance Forward from $date"),
|
||||
'comment' => "-- SUMMARY OF EXCLUDED ENTRIES --"),
|
||||
|
||||
'Transaction' => array('id' => null,
|
||||
//'stamp' => $date,
|
||||
'stamp' => null,
|
||||
'comment' => null),
|
||||
));
|
||||
}
|
||||
|
||||
$entries = $this->LedgerEntry->findInLedgerContext($id, $account_type, $cond, $link);
|
||||
/* pr(array('function' => 'Ledger::findLedgerEntries', */
|
||||
/* 'args' => compact('id', 'account_type', 'cond', 'link'), */
|
||||
/* 'vars' => compact('ledger'), */
|
||||
/* 'return' => compact('entries'), */
|
||||
/* )); */
|
||||
//pr(compact('entries'));
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -204,41 +138,36 @@ class Ledger extends AppModel {
|
||||
* function: stats
|
||||
* - Returns summary data from the requested ledger.
|
||||
*/
|
||||
function stats($id, $cond = null) {
|
||||
if (!isset($cond))
|
||||
$cond = array();
|
||||
$cond[] = array('Ledger.id' => $id);
|
||||
function stats($id, $query = null) {
|
||||
if (!$id)
|
||||
return null;
|
||||
|
||||
$stats = $this->find
|
||||
('first', array
|
||||
('link' =>
|
||||
array(// Models
|
||||
'Account' => array('fields' => array()),
|
||||
//'LedgerEntry' => array('fields' => array()),
|
||||
'LedgerEntry' =>
|
||||
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",
|
||||
"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"),
|
||||
'conditions' => $cond,
|
||||
'group' => 'Ledger.id',
|
||||
));
|
||||
$this->queryInit($query);
|
||||
|
||||
if (!isset($query['link']['Account']))
|
||||
$query['link']['Account'] = array();
|
||||
if (!isset($query['link']['Account']['fields']))
|
||||
$query['link']['Account']['fields'] = array();
|
||||
if (!isset($query['fields']))
|
||||
$query['fields'] = array();
|
||||
|
||||
$query['fields'] = array_merge($query['fields'],
|
||||
$this->debitCreditFields(true));
|
||||
|
||||
$query['conditions'][] = array('LedgerEntry.ledger_id' => $id);
|
||||
$query['group'][] = 'LedgerEntry.ledger_id';
|
||||
|
||||
$stats = $this->LedgerEntry->find('first', $query);
|
||||
|
||||
// The fields are all tucked into the [0] index,
|
||||
// and the rest of the array is useless (empty).
|
||||
$stats = $stats[0];
|
||||
|
||||
// Make sure we have a member for debit/credit
|
||||
foreach(array('debits', 'credits') AS $crdr)
|
||||
if (!isset($stats[$crdr]))
|
||||
$stats[$crdr] = null;
|
||||
|
||||
// Make sure we have a non-null balance
|
||||
if (!isset($stats['balance']))
|
||||
$stats['balance'] = 0;
|
||||
|
||||
@@ -1,523 +1,213 @@
|
||||
<?php
|
||||
class LedgerEntry extends AppModel {
|
||||
|
||||
var $name = 'LedgerEntry';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'transaction_id' => array('numeric'),
|
||||
'amount' => array('money')
|
||||
var $belongsTo = array(
|
||||
'Transaction',
|
||||
'Account',
|
||||
'Ledger',
|
||||
);
|
||||
|
||||
var $hasOne = array(
|
||||
'Tender',
|
||||
'DoubleEntry',
|
||||
);
|
||||
|
||||
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(
|
||||
// The Debit half of the double entry matching THIS Credit (if it is one)
|
||||
'DebitEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'joinTable' => 'reconciliations',
|
||||
'foreignKey' => 'credit_ledger_entry_id',
|
||||
'associationForeignKey' => 'debit_ledger_entry_id',
|
||||
'joinTable' => 'double_entries',
|
||||
'linkalias' => 'DDE',
|
||||
'foreignKey' => 'credit_entry_id',
|
||||
'associationForeignKey' => 'debit_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(
|
||||
|
||||
// The Credit half of the double entry matching THIS Debit (if it is one)
|
||||
'CreditEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'joinTable' => 'reconciliations',
|
||||
'foreignKey' => 'credit_ledger_entry_id',
|
||||
'associationForeignKey' => 'debit_ledger_entry_id',
|
||||
'joinTable' => 'double_entries',
|
||||
'linkalias' => 'CDE',
|
||||
'foreignKey' => 'debit_entry_id',
|
||||
'associationForeignKey' => 'credit_entry_id',
|
||||
),
|
||||
'CreditReconciliationLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'joinTable' => 'reconciliations',
|
||||
'foreignKey' => 'debit_ledger_entry_id',
|
||||
'associationForeignKey' => 'credit_ledger_entry_id',
|
||||
|
||||
//
|
||||
'StatementEntry' => array(
|
||||
'joinTable' => 'statement_fractions',
|
||||
'fields' => 'StatementFraction.amount',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: conditionEntryAsCreditOrDebit
|
||||
* - returns the condition necessary to match a set of
|
||||
* Ledgers to all related LedgerEntries
|
||||
* function: debitCreditFields
|
||||
* - Returns the fields necessary to determine whether the queried
|
||||
* entries are a debit, or a credit, and also the effect each have
|
||||
* on the overall balance of the account/ledger.
|
||||
*/
|
||||
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');
|
||||
function debitCreditFields($sum = false, $balance = true,
|
||||
$entry_name = 'LedgerEntry', $account_name = 'Account') {
|
||||
$fields = array
|
||||
(
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$entry_name}.crdr = 'DEBIT'," .
|
||||
" {$entry_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS debit' . ($sum ? 's' : ''),
|
||||
|
||||
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");
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$entry_name}.crdr = 'CREDIT'," .
|
||||
" {$entry_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS credit' . ($sum ? 's' : ''),
|
||||
);
|
||||
|
||||
if (isset($account_type)) {
|
||||
if (in_array($account_type, array('ASSET', 'EXPENSE')))
|
||||
$ledger_type = 'debit';
|
||||
else
|
||||
$ledger_type = 'credit';
|
||||
if ($balance)
|
||||
$fields[] =
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF(${account_name}.type IN ('ASSET', 'EXPENSE')," .
|
||||
" IF({$entry_name}.crdr = 'DEBIT', 1, -1)," .
|
||||
" IF({$entry_name}.crdr = 'CREDIT', 1, -1))" .
|
||||
" * IF({$entry_name}.amount, {$entry_name}.amount, 0)" .
|
||||
($sum ? ')' : '') . ' AS balance';
|
||||
|
||||
$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");
|
||||
}
|
||||
if ($sum)
|
||||
$fields[] = "COUNT({$entry_name}.id) AS entries";
|
||||
|
||||
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)),
|
||||
);
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyLedgerEntry
|
||||
* - Verifies consistenty of new ledger entry data
|
||||
* (not in a pre-existing ledger entry)
|
||||
*/
|
||||
function verifyLedgerEntry($entry, $tender = null) {
|
||||
/* pr(array("LedgerEntry::verifyLedgerEntry()" */
|
||||
/* => compact('entry', 'tender'))); */
|
||||
|
||||
if (empty($entry['account_id']) ||
|
||||
empty($entry['crdr']) ||
|
||||
empty($entry['amount'])
|
||||
) {
|
||||
/* pr(array("LedgerEntry::verifyLedgerEntry()" */
|
||||
/* => "Entry verification failed")); */
|
||||
return false;
|
||||
}
|
||||
|
||||
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')));
|
||||
if (isset($tender) && !$this->Tender->verifyTender($tender)) {
|
||||
/* pr(array("LedgerEntry::verifyLedgerEntry()" */
|
||||
/* => "Tender verification failed")); */
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addLedgerEntry
|
||||
* - Inserts new Ledger Entry into the database
|
||||
*/
|
||||
function addLedgerEntry($entry, $tender = null) {
|
||||
/* pr(array('LedgerEntry::addLedgerEntry' => */
|
||||
/* compact('entry', 'tender'))); */
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyLedgerEntry($entry, $tender))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
if (empty($entry['ledger_id']))
|
||||
$entry['ledger_id'] =
|
||||
$this->Account->currentLedgerID($entry['account_id']);
|
||||
|
||||
/* pr(array('LedgerEntry::addLedgerEntry' => */
|
||||
/* array('checkpoint' => 'Pre-Save') */
|
||||
/* + compact('entry'))); */
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($entry))
|
||||
return array('error' => true) + $ret;
|
||||
|
||||
$ret['ledger_entry_id'] = $this->id;
|
||||
|
||||
if (isset($tender)) {
|
||||
$tender['account_id'] = $entry['account_id'];
|
||||
$tender['ledger_entry_id'] = $ret['ledger_entry_id'];
|
||||
$result = $this->Tender->addTender($tender);
|
||||
$ret['Tender'] = $result;
|
||||
if ($result['error'])
|
||||
return array('error' => true) + $ret;
|
||||
}
|
||||
|
||||
return $ret + array('error' => false);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: stats
|
||||
* - Returns summary data from the requested ledger entry
|
||||
*/
|
||||
function stats($id) {
|
||||
function stats($id = null, $query = null, $set = null) {
|
||||
$this->queryInit($query);
|
||||
unset($query['group']);
|
||||
|
||||
$query = array
|
||||
(
|
||||
'fields' => array("SUM(Reconciliation.amount) AS 'reconciled'"),
|
||||
if (!isset($query['link']['DoubleEntry']))
|
||||
$query['link']['DoubleEntry'] = array();
|
||||
/* if (!isset($query['link']['DoubleEntry']['fields'])) */
|
||||
/* $query['link']['DoubleEntry']['fields'] = array(); */
|
||||
|
||||
'conditions' => array(isset($cond) ? $cond : array(),
|
||||
array('LedgerEntry.id' => $id)),
|
||||
if (isset($id))
|
||||
$query['conditions'][] = array('Entry.id' => $id);
|
||||
|
||||
'group' => 'LedgerEntry.id',
|
||||
);
|
||||
if (isset($set))
|
||||
$set = strtoupper($set);
|
||||
|
||||
// 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'];
|
||||
//pr(array('stats()', compact('id', 'query', 'set')));
|
||||
|
||||
// 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'];
|
||||
$rtypes = array('charge', 'payment',
|
||||
// 'debit', 'credit',
|
||||
);
|
||||
|
||||
$stats = array();
|
||||
foreach($rtypes AS $rtype) {
|
||||
$Rtype = ucfirst($rtype);
|
||||
|
||||
if (($rtype == 'charge' && (!isset($set) || $set == 'PAYMENT')) ||
|
||||
($rtype == 'payment' && (!isset($set) || $set == 'CHARGE'))
|
||||
/* ($rtype == 'debit' && (!isset($set) || $set == 'CREDIT')) || */
|
||||
/* ($rtype == 'credit' && (!isset($set) || $set == 'DEBIT')) */
|
||||
) {
|
||||
|
||||
$rquery = $query;
|
||||
$rquery['link'][$Rtype] =
|
||||
array('fields' => array("SUM(COALESCE(Applied{$Rtype}.amount,0)) AS reconciled"));
|
||||
|
||||
$rquery['fields'] = array();
|
||||
//$rquery['fields'][] = "SUM(DoubleEntry.amount) AS total";
|
||||
$rquery['fields'][] = "SUM(DoubleEntry.amount) - SUM(COALESCE(Applied{$Rtype}.amount,0)) AS balance";
|
||||
$rquery['conditions'][] = array("Applied{$Rtype}.id !=" => null);
|
||||
|
||||
$result = $this->find('first', $rquery);
|
||||
//pr(compact('Rtype', 'rquery', 'result'));
|
||||
|
||||
$sumfld = $Rtype;
|
||||
$stats[$sumfld] = $result[0];
|
||||
/* if (!isset($stats[$sumfld]['applied'])) */
|
||||
/* $stats[$sumfld]['applied'] = 0; */
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
<?php
|
||||
class MonetarySource extends AppModel {
|
||||
|
||||
var $name = 'MonetarySource';
|
||||
var $validate = array(
|
||||
'id' => array('numeric'),
|
||||
'name' => array('notempty'),
|
||||
'tillable' => array('boolean')
|
||||
);
|
||||
|
||||
var $belongsTo = array(
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: nsf
|
||||
* - Flags the ledger entry as having insufficient funds
|
||||
* - NOTE: nsf only works if given the monetary source id
|
||||
* to transaction e3, below
|
||||
* - NOTE: In order to show that the rent formerly considered
|
||||
* "collected" is now recognized in reverse, we must
|
||||
* credit A/R with a negative amount in order to
|
||||
* reconcile it against the Rent<->A/R ledger entry.
|
||||
*
|
||||
* FEE RENT A/R RECEIPT CHECK NSF BANK
|
||||
* ------- ------- ------- ------- ------- ------- -------
|
||||
* | |30 30| | | | | t1 e1a : R e2/e7a :
|
||||
* | |20 20| | | | | t1 e1b : R e2/e7b :
|
||||
* | | | | | | |
|
||||
* | | |30 30| | | | t2 e2a : R e3 : R e1a
|
||||
* | | |20 20| | | | t2 e2b : R e3 : R e1b
|
||||
* | | | |50 50| | | t2 e3 : R e4 : R e2
|
||||
* | | | | | | |
|
||||
* | | | | |50 | 50| t3 e4 : : R e3
|
||||
* | | | | | | |
|
||||
* | | | | | |-50 -50| t4 e5 : : R e6
|
||||
* | | | |-50 | -50| | t5 e6 : R e5 : R e7a/e7b
|
||||
* | | |-30 -30| | | | t6 e7a : R e6 : R e1a
|
||||
* | | |-20 -20| | | | t6 e7b : R e6 : R e1b
|
||||
* |35 | 35| | | | | t6 e8
|
||||
*
|
||||
*/
|
||||
|
||||
function nsf($id, $stamp = null) {
|
||||
pr(array('MonetarySource::nsf',
|
||||
compact('id')));
|
||||
|
||||
$A = new Account();
|
||||
|
||||
// Get the LedgerEntries that use this monetary source
|
||||
$source = $this->find
|
||||
('first',
|
||||
array('contain' =>
|
||||
array(/* e3 */
|
||||
'LedgerEntry' =>
|
||||
array('Transaction.id',
|
||||
'MonetarySource.id',
|
||||
'Customer.id',
|
||||
'Lease.id',
|
||||
|
||||
/* e3 debit */
|
||||
'DebitLedger' => /* e.g. CHECK Ledger */
|
||||
array('fields' => array(),
|
||||
|
||||
'Account' => /* e.g. CHECK Account */
|
||||
array('fields' => array('id', 'name'),
|
||||
'conditions' =>
|
||||
array('Account.payable' => 1,
|
||||
'Account.type' => 'ASSET'),
|
||||
),
|
||||
),
|
||||
|
||||
/* e3 credit */
|
||||
'CreditLedger' => /* i.e. RECEIPT Ledger */
|
||||
array('fields' => array('id'),
|
||||
'Account' => /* i.e. RECEIPT Account */
|
||||
array('fields' => array('id', 'name'),
|
||||
'conditions' =>
|
||||
array('Account.id' => $A->receiptAccountID()),
|
||||
),
|
||||
),
|
||||
|
||||
/* e2 */
|
||||
'DebitReconciliationLedgerEntry' =>
|
||||
array(/* e2 credit */
|
||||
'CreditLedger' => /* i.e. A/R Ledger */
|
||||
array('fields' => array(),
|
||||
|
||||
'Account' => /* i.e. A/R Account */
|
||||
array('fields' => array(),
|
||||
'conditions' =>
|
||||
array('Account.id' => $A->accountReceivableAccountID()),
|
||||
),
|
||||
),
|
||||
|
||||
/* e1 */
|
||||
// STUPID CakePHP bug screws up CLASS contains CLASS.
|
||||
// Use the same class, but with different name.
|
||||
'DebitReconciliationLedgerEntry2',
|
||||
),
|
||||
|
||||
/* e4 */
|
||||
'CreditReconciliationLedgerEntry' =>
|
||||
array(/* e4 debit */
|
||||
'DebitLedger' => /* e.g. BANK Ledger */
|
||||
array('fields' => array('id'),
|
||||
'Account' => /* e.g. BANK Account */
|
||||
array('fields' => array('id', 'name'),
|
||||
'conditions' =>
|
||||
array('Account.depositable' => 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'conditions' => array(array('MonetarySource.id' => $id)),
|
||||
));
|
||||
pr($source);
|
||||
|
||||
$nsf_account_id = $A->nsfAccountID();
|
||||
$nsf_fee_account_id = $A->nsfChargeAccountID();
|
||||
$ar_account_id = $A->accountReceivableAccountID();
|
||||
$receipt_account_id = $A->receiptAccountID();
|
||||
|
||||
$t4_id = null;
|
||||
$t5_id = null;
|
||||
foreach ($source['LedgerEntry'] AS $e3) {
|
||||
// We expect only a single e4 entry
|
||||
$e4 = $e3['CreditReconciliationLedgerEntry'];
|
||||
if (count($e4) < 1)
|
||||
continue;
|
||||
if (count($e4) > 1)
|
||||
die('Too many e4 entries');
|
||||
|
||||
// Pullup e4 from the single member array
|
||||
$e4 = $e4[0];
|
||||
|
||||
// e3 amount
|
||||
$amount = -$e3['amount'];
|
||||
|
||||
// e4 account
|
||||
$bank_account_id = $e4['DebitLedger']['account_id'];
|
||||
|
||||
// post new e5
|
||||
$e5_ids = $A->postLedgerEntry
|
||||
(array('transaction_id' => $t4_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($bank_account_id),
|
||||
'credit_ledger_id' => $A->currentLedgerID($nsf_account_id),
|
||||
'effective_date' => $stamp,
|
||||
'amount' => $amount,
|
||||
'lease_id' => $e3['lease_id'],
|
||||
'customer_id' => $e3['customer_id'],
|
||||
'comment' => "NSF Bank Reversal; Monetary Source #{$id}",
|
||||
)
|
||||
);
|
||||
|
||||
if ($e5_ids['error'])
|
||||
return null;
|
||||
$t4_id = $e5_ids['transaction_id'];
|
||||
|
||||
pr(array('checkpoint' => 'Posted Ledger Entry e5',
|
||||
compact('e5_ids', 'amount')));
|
||||
|
||||
// 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
|
||||
(array('transaction_id' => $t5_id),
|
||||
array('monetary_source_id' => $e3['monetary_source_id']),
|
||||
array('debit_ledger_id' => $A->currentLedgerID($nsf_account_id),
|
||||
'credit_ledger_id' => $A->currentLedgerID($receipt_account_id),
|
||||
'effective_date' => $stamp,
|
||||
'amount' => $amount,
|
||||
'lease_id' => $e3['lease_id'],
|
||||
'customer_id' => $e3['customer_id'],
|
||||
'comment' => "NSF tracker; Monetary Source #{$id}",
|
||||
),
|
||||
array('debit' => array
|
||||
(array('LedgerEntry' =>
|
||||
array('id' => $e5_ids['id'],
|
||||
'amount' => $amount))),
|
||||
)
|
||||
);
|
||||
|
||||
if ($e6_ids['error'])
|
||||
return null;
|
||||
$t5_id = $e6_ids['transaction_id'];
|
||||
|
||||
pr(array('checkpoint' => 'Posted Ledger Entry e6',
|
||||
compact('e6_ids', 'amount')));
|
||||
|
||||
$t6_id = null;
|
||||
foreach ($e3['DebitReconciliationLedgerEntry'] AS $e2) {
|
||||
foreach ($e2['DebitReconciliationLedgerEntry2'] AS $e1) {
|
||||
$amount = -1*$e1['Reconciliation']['amount'];
|
||||
|
||||
// post new e7
|
||||
$e7_ids = $A->postLedgerEntry
|
||||
(array('transaction_id' => $t6_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($receipt_account_id),
|
||||
'credit_ledger_id' => $A->currentLedgerID($ar_account_id),
|
||||
'effective_date' => $stamp,
|
||||
'amount' => $amount,
|
||||
'lease_id' => $e1['lease_id'],
|
||||
'customer_id' => $e1['customer_id'],
|
||||
'comment' => "NSF Receipt; Monetary Source #{$id}",
|
||||
),
|
||||
array('debit' => array
|
||||
(array('LedgerEntry' =>
|
||||
array('id' => $e6_ids['id'],
|
||||
'amount' => $amount))),
|
||||
|
||||
'credit' => array
|
||||
(array('LedgerEntry' =>
|
||||
array('id' => $e1['id'],
|
||||
'amount' => $amount))),
|
||||
)
|
||||
);
|
||||
|
||||
if ($e7_ids['error'])
|
||||
return null;
|
||||
$t6_id = $e7_ids['transaction_id'];
|
||||
|
||||
pr(array('checkpoint' => 'Posted Ledger Entry e7',
|
||||
compact('e7_ids', 'amount')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cheat for now
|
||||
$customer_id = $source['LedgerEntry'][0]['customer_id'];
|
||||
$lease_id = null;
|
||||
|
||||
// post new e8
|
||||
$e8_ids = $A->postLedgerEntry
|
||||
(array('transaction_id' => $t6_id),
|
||||
null,
|
||||
array('debit_ledger_id' => $A->currentLedgerID($ar_account_id),
|
||||
'credit_ledger_id' => $A->currentLedgerID($nsf_fee_account_id),
|
||||
'effective_date' => $stamp,
|
||||
'amount' => 35,
|
||||
'lease_id' => $lease_id,
|
||||
'customer_id' => $customer_id,
|
||||
'comment' => "NSF Fee; Monetary Source #{$id}",
|
||||
)
|
||||
);
|
||||
|
||||
if ($e8_ids['error'])
|
||||
return null;
|
||||
|
||||
pr(array('checkpoint' => 'Posted Ledger Entry e8',
|
||||
compact('e8_ids')));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
class Reconciliation extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'DebitLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
//'foreignKey' => 'credit_ledger_entry_id',
|
||||
),
|
||||
'CreditLedgerEntry' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
//'foreignKey' => 'credit_ledger_entry_id',
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
605
site/models/statement_entry.php
Normal file
605
site/models/statement_entry.php
Normal file
@@ -0,0 +1,605 @@
|
||||
<?php
|
||||
class StatementEntry extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'Transaction',
|
||||
'Customer',
|
||||
'Lease',
|
||||
|
||||
// The charge to which this payment applies (if it is one)
|
||||
'ChargeEntry' => array(
|
||||
'className' => 'StatementEntry',
|
||||
),
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
// The payments that apply to this charge (if it is one)
|
||||
'PaymentEntry' => array(
|
||||
'className' => 'StatementEntry',
|
||||
'foreignKey' => 'charge_entry_id',
|
||||
),
|
||||
|
||||
'StatementFraction',
|
||||
);
|
||||
|
||||
var $hasAndBelongsToMany = array(
|
||||
'LedgerEntry' => array(
|
||||
'joinTable' => 'statement_fractions',
|
||||
//'fields' => 'StatementFraction.amount',
|
||||
),
|
||||
);
|
||||
|
||||
//var $default_log_level = 30;
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: chargePaymentFields
|
||||
*/
|
||||
|
||||
|
||||
function chargePaymentFields($sum = false, $entry_name = 'StatementEntry') {
|
||||
$fields = array
|
||||
(
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$entry_name}.type = 'CHARGE'," .
|
||||
" {$entry_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS charge' . ($sum ? 's' : ''),
|
||||
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$entry_name}.type = 'PAYMENT' OR {$entry_name}.type = 'SURPLUS'," .
|
||||
" {$entry_name}.amount, NULL)" .
|
||||
($sum ? ')' : '') . ' AS payment' . ($sum ? 's' : ''),
|
||||
|
||||
($sum ? 'SUM(' : '') .
|
||||
"IF({$entry_name}.type = 'CHARGE', 1," .
|
||||
" IF({$entry_name}.type = 'PAYMENT' OR {$entry_name}.type = 'SURPLUS', -1, 0))" .
|
||||
" * IF({$entry_name}.amount, {$entry_name}.amount, 0)" .
|
||||
($sum ? ')' : '') . ' AS balance',
|
||||
);
|
||||
|
||||
if ($sum)
|
||||
$fields[] = "COUNT({$entry_name}.id) AS entries";
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyStatementEntry
|
||||
* - Verifies consistenty of new statement entry data
|
||||
* (not in a pre-existing statement entry)
|
||||
*/
|
||||
function verifyStatementEntry($entry) {
|
||||
$this->prFunctionLevel(30);
|
||||
$this->prEnter(compact('entry'));
|
||||
|
||||
if (empty($entry['type']) ||
|
||||
//empty($entry['effective_date']) ||
|
||||
empty($entry['amount'])
|
||||
) {
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addStatementEntry
|
||||
* - Inserts new Statement Entry into the database
|
||||
*/
|
||||
function addStatementEntry($entry) {
|
||||
$this->prEnter(compact('entry'));
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyStatementEntry($entry))
|
||||
return array('error' => true, 'verify_data' => $entry) + $ret;
|
||||
|
||||
$this->pr(20, array('checkpoint' => 'Pre-Save')
|
||||
+ compact('entry'));
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($entry))
|
||||
return array('error' => true, 'save_data' => $entry) + $ret;
|
||||
|
||||
foreach ($entry['Fraction'] AS $fraction) {
|
||||
$fraction['statement_entry_id'] = $this->id;
|
||||
$this->StatementFraction->id = null;
|
||||
$this->StatementFraction->save($fraction);
|
||||
}
|
||||
|
||||
$ret['statement_entry_id'] = $this->id;
|
||||
return $this->prReturn($ret + array('error' => false));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reverse
|
||||
* - Reverses the charges
|
||||
*
|
||||
* SAMPLE MOVE IN w/ PRE PAYMENT
|
||||
* DEPOSIT RENT A/R RECEIPT CHECK PETTY BANK
|
||||
* ------- ------- ------- ------- ------- ------- -------
|
||||
* |25 | 25| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | |20 20| | | | |
|
||||
* | | |25 25| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | |20 20| | | |
|
||||
* | | | |85 85| | |
|
||||
* | | | | |85 | 85|
|
||||
|
||||
* MOVE OUT and REFUND FINAL MONTH
|
||||
* DEPOSIT RENT C/P RECEIPT CHECK PETTY BANK
|
||||
* ------- ------- ------- ------- ------- ------- -------
|
||||
* 25| | |25 | | | | t20 e20a
|
||||
* | 20| |20 | | | | t20 e20b
|
||||
|
||||
* -ONE REFUND CHECK-
|
||||
* | | 25| |25 | | | t30 e30a
|
||||
* | | 20| |20 | | | t30 e30b
|
||||
* | | | 45| | | |45 t40 e40
|
||||
* -OR MULTIPLE-
|
||||
* | | 15| |15 | | | t50a e50a
|
||||
* | | | 15| | |15 | t60a e60a
|
||||
* | | 30| |30 | | | t50b e50b
|
||||
* | | | 30| | | |30 t60b e60b
|
||||
* | | | | | | |
|
||||
|
||||
|
||||
OPTION 1
|
||||
* |-25 | -25| | | | |
|
||||
* | |-20 -20| | | | |
|
||||
* | | |-25 -25| | | |
|
||||
* | | |-20 -20| | | |
|
||||
|
||||
OPTION 2
|
||||
* |-25 | | -25| | | |
|
||||
* | |-20 | -20| | | |
|
||||
* | | | |-15 | -15| |
|
||||
* | | | |-30 | | -30|
|
||||
* | | | | | | |
|
||||
*
|
||||
*/
|
||||
function reverse($ledger_entries, $stamp = null) {
|
||||
$this->prEnter(compact('ledger_entries', 'stamp'));
|
||||
|
||||
// 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 $this->prReturn(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 $this->prReturn(null);
|
||||
$transaction_id = $ids['transaction_id'];
|
||||
|
||||
$this->pr(15, compact('ids', 'amount', 'refund_account_id', 'ar_account_id'),
|
||||
'Posted Refund Ledger Entry');
|
||||
}
|
||||
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconciledSet
|
||||
* - Returns the set of entries satisfying the given conditions,
|
||||
* along with any entries that they reconcile
|
||||
*/
|
||||
function reconciledSetQuery($set, $query) {
|
||||
$this->queryInit($query);
|
||||
|
||||
if ($set == 'CHARGE' || $set == 'PAYMENT')
|
||||
$query['conditions'][] = array('StatementEntry.type' => $set);
|
||||
else
|
||||
die("INVALID RECONCILE SET");
|
||||
|
||||
if ($set == 'CHARGE')
|
||||
$query['link']['PaymentEntry'] = array('fields' => array("SUM(PaymentEntry.amount) AS reconciled"));
|
||||
if ($set == 'PAYMENT')
|
||||
$query['link']['ChargeEntry'] = array('fields' => array("SUM(ChargeEntry.amount) AS reconciled"));
|
||||
|
||||
$query['group'] = 'StatementEntry.id';
|
||||
|
||||
// REVISIT: TESTING
|
||||
//$query['link']['PaymentEntry'] = array('fields' => array("(`PaymentEntry.amount`+0) AS reconciled"));
|
||||
//$query['group'] = null;
|
||||
// END REVISIT
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
function reconciledSet($set, $query = null, $unrec = false, $if_rec_include_partial = false) {
|
||||
//$this->prFunctionLevel(16);
|
||||
$this->prEnter(compact('set', 'query', 'unrec', 'if_rec_include_partial'));
|
||||
$lquery = $this->reconciledSetQuery($set, $query);
|
||||
$result = $this->find('all', $lquery);
|
||||
|
||||
$this->pr(20, compact('lquery', 'result'));
|
||||
|
||||
$resultset = array();
|
||||
foreach ($result AS $i => $entry) {
|
||||
$this->pr(25, compact('entry'));
|
||||
if (!empty($entry[0]))
|
||||
$entry['StatementEntry'] = $entry[0] + $entry['StatementEntry'];
|
||||
unset($entry[0]);
|
||||
|
||||
$entry['StatementEntry']['balance'] =
|
||||
$entry['StatementEntry']['amount'] - $entry['StatementEntry']['reconciled'];
|
||||
|
||||
// Since HAVING isn't a builtin feature of CakePHP,
|
||||
// we'll have to post-process to get the desired entries
|
||||
|
||||
if ($entry['StatementEntry']['balance'] == 0)
|
||||
$reconciled = true;
|
||||
elseif ($entry['StatementEntry']['reconciled'] == 0)
|
||||
$reconciled = false;
|
||||
else // Partial payment; depends on unrec
|
||||
$reconciled = (!$unrec && $if_rec_include_partial);
|
||||
|
||||
// Add to the set, if it's been requested
|
||||
if ($reconciled == !$unrec)
|
||||
$resultset[] = $entry;
|
||||
}
|
||||
|
||||
return $this->prReturn(array('entries' => $resultset,
|
||||
'summary' => $this->stats(null, $query)));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: reconciledEntries
|
||||
* - Returns a list of entries that reconcile against the given entry.
|
||||
* (such as payments towards a charge).
|
||||
*/
|
||||
function reconciledEntriesQuery($id, $query = null) {
|
||||
$this->queryInit($query, false);
|
||||
|
||||
$this->id = $id;
|
||||
$this->recursive = -1;
|
||||
$this->read();
|
||||
|
||||
$query['conditions'][] = array('StatementEntry.id' => $id);
|
||||
|
||||
if ($this->data['StatementEntry']['type'] == 'CHARGE')
|
||||
$query['link']['PaymentEntry'] = array();
|
||||
if ($this->data['StatementEntry']['type'] == 'PAYMENT')
|
||||
$query['link']['ChargeEntry'] = array();
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
function reconciledEntries($id, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
$lquery = $this->reconciledEntriesQuery($id, $query);
|
||||
|
||||
$result = $this->find('all', $lquery);
|
||||
foreach (array_keys($result) AS $i)
|
||||
unset($result[$i]['StatementEntry']);
|
||||
|
||||
return $this->prReturn(array('entries' => $result));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: assignCredits
|
||||
* - Assigns all credits to existing charges
|
||||
*
|
||||
* REVISIT <AP>: 20090726
|
||||
* This algorithm shouldn't be hardcoded. We need to allow
|
||||
* the user to specify how payments should be applied.
|
||||
*
|
||||
*/
|
||||
function assignCredits($query = null, $receipt_id = null) {
|
||||
$this->prFunctionLevel(25);
|
||||
$this->prEnter( compact('query', 'receipt_id'));
|
||||
$this->queryInit($query);
|
||||
|
||||
$ret = array();
|
||||
|
||||
// First, find all known credits
|
||||
$lquery = $query;
|
||||
$lquery['conditions'][] = array('StatementEntry.type' => 'SURPLUS');
|
||||
$lquery['order'][] = 'StatementEntry.effective_date ASC';
|
||||
$credits = $this->find('all', $lquery);
|
||||
$this->pr(18, compact('credits'),
|
||||
"Credits Established");
|
||||
|
||||
// Next, establish credit from the newly added receipt
|
||||
$receipt_credit = null;
|
||||
if (!empty($receipt_id)) {
|
||||
$lquery = array
|
||||
('contain' => array
|
||||
('LedgerEntry' =>
|
||||
array('conditions' =>
|
||||
//array(LedgerEntry.'crdr'=>'DEBIT'),
|
||||
array('LedgerEntry.account_id !=' =>
|
||||
$this->LedgerEntry->Account->accountReceivableAccountID()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->Transaction->id = $receipt_id;
|
||||
$receipt_credit = $this->Transaction->find('first', $lquery);
|
||||
if (!$receipt_credit)
|
||||
die("INTERNAL ERROR: UNABLE TO LOCATE RECEIPT");
|
||||
|
||||
$receipt_credit['balance'] = $receipt_credit['Transaction']['amount'];
|
||||
|
||||
$this->pr(18, compact('receipt_credit'),
|
||||
"Receipt Credit Added");
|
||||
}
|
||||
|
||||
// Now find all unpaid charges
|
||||
$lquery = $query;
|
||||
$lquery['order'] = 'StatementEntry.effective_date ASC';
|
||||
$charges = $this->reconciledSet('CHARGE', $lquery, true);
|
||||
$this->pr(18, compact('charges'),
|
||||
"Outstanding Charges Determined");
|
||||
|
||||
// Initialize our list of used credits
|
||||
$used_credits = array();
|
||||
|
||||
// Work through all unpaid charges, applying payments as we go
|
||||
foreach ($charges['entries'] AS $charge) {
|
||||
|
||||
$this->pr(20, compact('charge'),
|
||||
'Process Charge');
|
||||
|
||||
// Check that we have available credits.
|
||||
// Technically, this isn't necessary, since the loop
|
||||
// will handle everything just fine. However, this
|
||||
// just saves extra processing if/when there is no
|
||||
// means to resolve a charge anyway.
|
||||
if (count($credits) == 0 && empty($receipt_credit['balance'])) {
|
||||
$this->pr(15, 'No more available credits');
|
||||
break;
|
||||
}
|
||||
|
||||
$charge['balance'] = $charge['StatementEntry']['balance'];
|
||||
while ($charge['balance'] > 0 &&
|
||||
(count($credits) || !empty($receipt_credit['balance']))) {
|
||||
|
||||
$this->pr(20, compact('charge'),
|
||||
'Attempt Charge Reconciliation');
|
||||
|
||||
// Use explicit credits before using implicit credits
|
||||
// (Not sure it matters though).
|
||||
if (count($credits)) {
|
||||
// Peel off the first credit available
|
||||
$credit =& $credits[0];
|
||||
$payment_date = $credit['StatementEntry']['effective_date'];
|
||||
$payment_transaction_id = $credit['StatementEntry']['transaction_id'];
|
||||
$payment_account_id = $credit['StatementEntry']['account_id'];
|
||||
|
||||
if (!isset($credit['balance']))
|
||||
$credit['balance'] = $credit['StatementEntry']['amount'];
|
||||
}
|
||||
elseif (!empty($receipt_credit['balance'])) {
|
||||
// Use our only receipt credit
|
||||
$credit =& $receipt_credit;
|
||||
$payment_date = $credit['Transaction']['stamp'];
|
||||
$payment_transaction_id = $credit['Transaction']['id'];
|
||||
$payment_account_id = $credit['LedgerEntry']['account_id'];
|
||||
}
|
||||
else {
|
||||
die("HOW DID WE GET HERE WITH NO SURPLUS?");
|
||||
}
|
||||
|
||||
// Set the payment amount to the maximum amount
|
||||
// possible without exceeding the charge or credit balance
|
||||
$payment_amount = min($charge['balance'], $credit['balance']);
|
||||
if (!isset($credit['applied']))
|
||||
$credit['applied'] = 0;
|
||||
|
||||
$credit['applied'] += $payment_amount;
|
||||
$credit['balance'] -= $payment_amount;
|
||||
|
||||
$this->pr(20, compact('credit'),
|
||||
($credit['balance'] > 0 ? 'Utilized' : 'Exhausted') .
|
||||
(count($credits) ? ' Credit' : ' Receipt'));
|
||||
|
||||
if ($credit['balance'] < 0)
|
||||
die("HOW DID WE END UP WITH NEGATIVE SURPLUS BALANCE?");
|
||||
|
||||
// If we've exhausted the credit, get it out of the
|
||||
// available credit pool (but keep track of it for later).
|
||||
if ($credit['balance'] <= 0 && count($credits))
|
||||
$used_credits[] = array_shift($credits);
|
||||
|
||||
// Add a payment that uses the available credit to pay the charge
|
||||
$payment = array('type' => 'PAYMENT',
|
||||
'account_id' => $payment_account_id,
|
||||
'amount' => $payment_amount,
|
||||
'effective_date' => $payment_date,
|
||||
'transaction_id' => $payment_transaction_id,
|
||||
'customer_id' => $charge['StatementEntry']['customer_id'],
|
||||
'lease_id' => $charge['StatementEntry']['lease_id'],
|
||||
'charge_entry_id' => $charge['StatementEntry']['id'],
|
||||
'comment' => null,
|
||||
);
|
||||
|
||||
$this->pr(20, compact('payment'),
|
||||
'New Payment Entry');
|
||||
|
||||
$result = $this->addStatementEntry($payment);
|
||||
$ret['Payment'][] = $result;
|
||||
if ($result['error'])
|
||||
$ret['error'] = true;
|
||||
|
||||
// Adjust the charge balance to reflect the new payment
|
||||
$charge['balance'] -= $payment_amount;
|
||||
if ($charge['balance'] < 0)
|
||||
die("HOW DID WE GET A NEGATIVE CHARGE AMOUNT?");
|
||||
|
||||
if ($charge['balance'] <= 0)
|
||||
$this->pr(20, 'Fully Paid Charge');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Partially used credits must be added to the used list
|
||||
if (isset($credits[0]['applied']))
|
||||
$used_credits[] = array_shift($credits);
|
||||
|
||||
$this->pr(18, compact('credits', 'used_credits', 'receipt_credit'),
|
||||
'Payments added');
|
||||
|
||||
// Clean up any explicit credits that have been used
|
||||
foreach ($used_credits AS $credit) {
|
||||
if ($credit['balance'] > 0) {
|
||||
$this->pr(20, compact('credit'),
|
||||
'Update Credit Entry');
|
||||
|
||||
$this->id = $credit['StatementEntry']['id'];
|
||||
$this->saveField('amount', $credit['balance']);
|
||||
}
|
||||
else {
|
||||
$this->pr(20, compact('credit'),
|
||||
'Delete Exhausted Credit Entry');
|
||||
|
||||
$this->del($credit['StatementEntry']['id'], false);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert non-exhausted receipt credit to an explicit one
|
||||
if (!empty($receipt_credit['balance'])) {
|
||||
$credit =& $receipt_credit;
|
||||
|
||||
$this->pr(18, compact('credit'),
|
||||
'Create Explicit Credit');
|
||||
|
||||
$result = $this->addStatementEntry
|
||||
(array('type' => 'SURPLUS',
|
||||
'account_id' => $credit['LedgerEntry']['account_id'],
|
||||
'amount' => $credit['balance'],
|
||||
'effective_date' => $credit['Transaction']['stamp'],
|
||||
'transaction_id' => $credit['Transaction']['id'],
|
||||
'customer_id' => $credit['Customer']['id'],
|
||||
));
|
||||
$ret['Credit'] = $result;
|
||||
if ($result['error'])
|
||||
$ret['error'] = true;
|
||||
}
|
||||
|
||||
return $this->prReturn($ret + array('error' => false));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: stats
|
||||
* - Returns summary data from the requested statement entry
|
||||
*/
|
||||
function stats($id = null, $query = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
|
||||
$this->queryInit($query);
|
||||
unset($query['group']);
|
||||
|
||||
$stats = array();
|
||||
if (isset($id))
|
||||
$query['conditions'][] = array('StatementEntry.id' => $id);
|
||||
|
||||
$rquery = $query;
|
||||
unset($rquery['link']['ChargeEntry']);
|
||||
$rquery['link']['PaymentEntry'] = array('fields' => array());
|
||||
|
||||
$rquery['fields'] = array();
|
||||
$rquery['fields'][] = "StatementEntry.amount";
|
||||
$rquery['fields'][] = "SUM(PaymentEntry.amount) AS reconciled";
|
||||
|
||||
$rquery['conditions'][] = array('StatementEntry.type' => 'CHARGE');
|
||||
$rquery['group'] = 'StatementEntry.id';
|
||||
|
||||
$result = $this->find('all', $rquery);
|
||||
$stats['Charge'] = array('total' => 0, 'reconciled' => 0);
|
||||
foreach($result AS $charge) {
|
||||
$stats['Charge']['total'] += $charge['StatementEntry']['amount'];
|
||||
$stats['Charge']['reconciled'] += $charge[0]['reconciled'];
|
||||
}
|
||||
$stats['Charge']['balance'] =
|
||||
$stats['Charge']['total'] - $stats['Charge']['reconciled'];
|
||||
|
||||
$this->pr(17, compact('query', 'result'),
|
||||
'Charges');
|
||||
|
||||
$rquery = $query;
|
||||
unset($rquery['link']['PaymentEntry']);
|
||||
$rquery['link']['ChargeEntry'] = array('fields' => array());
|
||||
|
||||
$rquery['fields'] = array();
|
||||
$rquery['fields'][] = "SUM(StatementEntry.amount) AS total";
|
||||
$rquery['fields'][] = "SUM(IF(ChargeEntry.id IS NULL, 0, StatementEntry.amount)) AS reconciled";
|
||||
$rquery['fields'][] = "SUM(IF(ChargeEntry.id IS NULL, StatementEntry.amount, 0)) AS balance";
|
||||
|
||||
$rquery['conditions'][] = array('StatementEntry.type' => 'PAYMENT');
|
||||
$result = $this->find('first', $rquery);
|
||||
if (!isset($result[0]['balance']))
|
||||
$result[0]['balance'] = 0;
|
||||
$stats['Payment'] = $result[0];
|
||||
|
||||
$this->pr(17, compact('rquery', 'result'),
|
||||
'Payments');
|
||||
|
||||
return $this->prReturn($stats);
|
||||
}
|
||||
|
||||
}
|
||||
9
site/models/statement_fraction.php
Normal file
9
site/models/statement_fraction.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
class StatementFraction extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'StatementEntry',
|
||||
'LedgerEntry',
|
||||
);
|
||||
|
||||
}
|
||||
145
site/models/tender.php
Normal file
145
site/models/tender.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
class Tender extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'TenderType',
|
||||
'Customer',
|
||||
'LedgerEntry',
|
||||
'DepositTransaction' => array(
|
||||
'className' => 'Transaction',
|
||||
),
|
||||
'NsfTransaction' => array(
|
||||
'className' => 'Transaction',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyTender
|
||||
* - Verifies consistenty of new tender data
|
||||
* (not in a pre-existing tender)
|
||||
*/
|
||||
function verifyTender($tender) {
|
||||
$this->prFunctionLevel(10);
|
||||
$this->prEnter(compact('tender'));
|
||||
|
||||
if (empty($tender['tender_type_id'])) {
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addTender
|
||||
* - Inserts new Tender into the database
|
||||
*/
|
||||
|
||||
function addTender($tender) {
|
||||
$this->prEnter(compact('tender'));
|
||||
|
||||
$ret = array();
|
||||
if (!$this->verifyTender($tender))
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Come up with a (not necessarily unique) name for the tender.
|
||||
// For checks & money orders, this will be based on the check
|
||||
// number. For other types of tender, we'll just use the
|
||||
// generic name of the monetary account.
|
||||
// REVISIT <AP>: 20090723
|
||||
// I would like to have cash named "Cash #1234", where
|
||||
// the number would correspond to either the Tender ID
|
||||
// or the LedgerEntry ID.
|
||||
if (empty($tender['name']) && !empty($tender['account_id'])) {
|
||||
$tender['name'] = $this->LedgerEntry->Account->name($tender['account_id']);
|
||||
if ($tender['account_id'] == $this->LedgerEntry->Account->checkAccountID() ||
|
||||
$tender['account_id'] == $this->LedgerEntry->Account->moneyOrderAccountID()) {
|
||||
$tender['name'] .= ' #' . $tender['data1'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->pr(20, array('Tender' => $tender),
|
||||
'Pre-Save');
|
||||
|
||||
$this->create();
|
||||
if (!$this->save($tender))
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
$ret['tender_id'] = $this->id;
|
||||
return $this->prReturn($ret + array('error' => false));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: nsf
|
||||
* - Flags the ledger entry as having insufficient funds
|
||||
*
|
||||
* Steps:
|
||||
* - Get information from Check (C1); for amount $A
|
||||
* - Find Bank Deposit matching to Tender
|
||||
* - New Transaction (T1)
|
||||
* - New Bank Deposit (D1)
|
||||
* - New Tender (N1); NSF; D1,
|
||||
* - Add new LedgerEntry (L1a); T1; debit:bank; -$A
|
||||
* - Add new LedgerEntry (L1b); T1; credit:NSF; -$A
|
||||
* - Add new LedgerEntry (L2a); T1; debit:NSF; -$A; N1
|
||||
* - Add new LedgerEntry (L2b); T1; credit:A/R; -$A
|
||||
* - For Tx associated with LE associated with C1:
|
||||
* - For each Payment SE of Tx:
|
||||
* - Add new StatementEntry (S1n); T1; PAYMENT; -1*S1n.amount
|
||||
* - New Transaction (T2) (?????)
|
||||
* - Add new StatementEntry (S2); T2; CHARGE; NSF; $35
|
||||
* - Add new LedgerEntry (L3a); T2; credit:NSF-Fee; $35
|
||||
* - Add new LedgerEntry (L3b); T2; debit:A/R; $35
|
||||
* - Set C1.nsf_tx = T1
|
||||
* - Re-Reconcile (customer may have running credit)
|
||||
*/
|
||||
|
||||
function nsf($id, $stamp = null) {
|
||||
$this->prFunctionLevel(30);
|
||||
$this->prEnter(compact('id'));
|
||||
|
||||
// Get information about this NSF item.
|
||||
$this->id = $id;
|
||||
$tender = $this->find
|
||||
('first', array
|
||||
('contain' =>
|
||||
array('LedgerEntry',
|
||||
'DepositTransaction',
|
||||
'NsfTransaction'),
|
||||
));
|
||||
$this->pr(20, compact('tender'));
|
||||
|
||||
if (!empty($tender['NsfTransaction']['id']))
|
||||
die("Item has already been set as NSF");
|
||||
|
||||
if (empty($tender['DepositTransaction']['id']))
|
||||
die("Item has not been deposited yet");
|
||||
|
||||
$tender['Transaction'] = $tender['DepositTransaction'];
|
||||
unset($tender['DepositTransaction']);
|
||||
unset($tender['NsfTransaction']);
|
||||
|
||||
$T = new Transaction();
|
||||
$result = $T->addNsf($tender, $stamp);
|
||||
if ($result['error'])
|
||||
return $this->prReturn(false);
|
||||
|
||||
// Flag the tender as NSF, using the items created from addNsf
|
||||
$this->id = $id;
|
||||
$this->saveField('nsf_transaction_id', $result['nsf_transaction_id']);
|
||||
$this->saveField('nsf_ledger_entry_id', $result['nsf_ledger_entry_id']);
|
||||
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
115
site/models/tender_type.php
Normal file
115
site/models/tender_type.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
class TenderType extends AppModel {
|
||||
|
||||
var $belongsTo = array(
|
||||
'Account',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'Tender',
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: accountID
|
||||
* - Returns the intended account ID for receipt of the given tender
|
||||
*/
|
||||
|
||||
function accountID($id) {
|
||||
$this->cacheQueries = true;
|
||||
$item = $this->find('first', array
|
||||
('contain' => false,
|
||||
'conditions' => array('TenderType.id' => $id),
|
||||
));
|
||||
$this->cacheQueries = false;
|
||||
return $item['TenderType']['account_id'];
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: paymentTypes
|
||||
* - Returns an array of types that can be used for payments
|
||||
*/
|
||||
|
||||
function paymentTypes($query = null) {
|
||||
$this->queryInit($query);
|
||||
$query['order'][] = 'name';
|
||||
|
||||
$this->cacheQueries = true;
|
||||
$types = $this->find('all', $query);
|
||||
$this->cacheQueries = false;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: paymentTypes
|
||||
* - Returns an array of types that can be deposited
|
||||
*/
|
||||
|
||||
function depositTypes($query = null) {
|
||||
$this->queryInit($query);
|
||||
$query['order'][] = 'name';
|
||||
$query['conditions'][] = array('tillable' => true);
|
||||
|
||||
$this->cacheQueries = true;
|
||||
$types = $this->find('all', $query);
|
||||
$this->cacheQueries = false;
|
||||
|
||||
// Rearrange to be of the form (id => name)
|
||||
$result = array();
|
||||
foreach ($types AS $type)
|
||||
$result[$type['TenderType']['id']] = $type['TenderType']['name'];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: defaultPaymentType
|
||||
* - Returns the ID of the default payment type
|
||||
*/
|
||||
|
||||
function defaultPaymentType() {
|
||||
return $this->nameToID('Check');
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: stats
|
||||
* - Returns the stats for the given tender type
|
||||
*/
|
||||
|
||||
function stats($id = null, $query = null) {
|
||||
if (!$id)
|
||||
return null;
|
||||
|
||||
$this->queryInit($query);
|
||||
|
||||
if (!isset($query['link']['Tender']))
|
||||
$query['link']['Tender'] = array('fields' => array());
|
||||
if (!isset($query['link']['Tender']['LedgerEntry']))
|
||||
$query['link']['Tender']['LedgerEntry'] = array('fields' => array());
|
||||
|
||||
$query['fields'][] = "SUM(COALESCE(LedgerEntry.amount,0)) AS 'total'";
|
||||
$query['fields'][] = "SUM(IF(deposit_transaction_id IS NULL, COALESCE(LedgerEntry.amount,0), 0)) AS 'undeposited'";
|
||||
$query['fields'][] = "SUM(IF(deposit_transaction_id IS NULL, 0, COALESCE(LedgerEntry.amount,0))) AS 'deposited'";
|
||||
$query['fields'][] = "SUM(IF(nsf_transaction_id IS NULL, 0, COALESCE(LedgerEntry.amount,0))) AS 'nsf'";
|
||||
|
||||
$this->id = $id;
|
||||
$stats = $this->find('first', $query);
|
||||
return $stats[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +1,73 @@
|
||||
<?php
|
||||
class Transaction extends AppModel {
|
||||
|
||||
var $name = 'Transaction';
|
||||
|
||||
var $validate = array(
|
||||
'stamp' => array('date')
|
||||
);
|
||||
|
||||
var $belongsTo = array(
|
||||
'Account',
|
||||
'Ledger',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'LedgerEntry',
|
||||
'StatementEntry',
|
||||
'DepositTender' => array(
|
||||
'className' => 'Tender',
|
||||
'foreignKey' => 'deposit_transaction_id',
|
||||
),
|
||||
|
||||
'Charge' => array(
|
||||
'className' => 'StatementEntry',
|
||||
'conditions' => array('Charge.type' => 'CHARGE')
|
||||
),
|
||||
|
||||
'Payment' => array(
|
||||
'className' => 'StatementEntry',
|
||||
'conditions' => array('Payment.type' => 'PAYMENT')
|
||||
),
|
||||
|
||||
'Debit' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'conditions' => array('Debit.crdr' => 'DEBIT')
|
||||
),
|
||||
|
||||
'Credit' => array(
|
||||
'className' => 'LedgerEntry',
|
||||
'conditions' => array('Credit.crdr' => 'CREDIT')
|
||||
),
|
||||
|
||||
);
|
||||
|
||||
|
||||
var $default_log_level = 30;
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addInvoice
|
||||
* - Adds a new invoice transaction
|
||||
* - Adds a new invoice invoice
|
||||
*/
|
||||
|
||||
function addInvoice($data, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
||||
|
||||
//pr(compact('data', 'customer_id', 'lease_id'));
|
||||
// Establish the transaction as an invoice
|
||||
$invoice =& $data['Transaction'];
|
||||
$invoice['type'] = 'INVOICE';
|
||||
$invoice['crdr'] = 'DEBIT';
|
||||
$invoice['account_id'] = $this->Account->accountReceivableAccountID();
|
||||
$invoice['customer_id'] = $customer_id;
|
||||
$invoice['lease_id'] = $lease_id;
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
|
||||
// Determine the total charges on the invoice
|
||||
$grand_total = 0;
|
||||
foreach ($data['LedgerEntry'] AS $entry)
|
||||
$grand_total += $entry['amount'];
|
||||
|
||||
// Go through the entered charges
|
||||
$invoice_transaction = array_intersect_key($data, array('Transaction'=>1, 'transaction_id'=>1));
|
||||
foreach ($data['LedgerEntry'] 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)),
|
||||
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
|
||||
);
|
||||
|
||||
if ($ids['error'])
|
||||
$ret = false;
|
||||
|
||||
$invoice_transaction = array_intersect_key($ids, array('transaction_id'=>1));
|
||||
// Go through the statement entries and flag as charges
|
||||
foreach ($data['Entry'] AS &$entry) {
|
||||
$entry['type'] = 'CHARGE';
|
||||
$entry['crdr'] = 'CREDIT';
|
||||
}
|
||||
|
||||
return $ret;
|
||||
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
|
||||
if (isset($ids['transaction_id']))
|
||||
$ids['invoice_id'] = $ids['transaction_id'];
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,42 +75,652 @@ class Transaction extends AppModel {
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addReceipt
|
||||
* - Adds a new receipt transaction
|
||||
* - Adds a new receipt
|
||||
*/
|
||||
|
||||
function addReceipt($data, $customer_id, $lease_id = null) {
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
||||
|
||||
// Assume this will succeed
|
||||
$ret = true;
|
||||
// Establish the transaction as a receipt
|
||||
$receipt =& $data['Transaction'];
|
||||
$receipt['type'] = 'RECEIPT';
|
||||
$receipt['crdr'] = 'CREDIT';
|
||||
$receipt['account_id'] = $this->Account->accountReceivableAccountID();
|
||||
$receipt['customer_id'] = $customer_id;
|
||||
$receipt['lease_id'] = $lease_id;
|
||||
|
||||
// Go through the entered payments
|
||||
$receipt_transaction = array_intersect_key($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));
|
||||
// Go through the statement entries and flag as payments
|
||||
foreach ($data['Entry'] AS &$entry) {
|
||||
$entry['crdr'] = 'DEBIT';
|
||||
if (empty($entry['account_id']) && isset($entry['Tender']['tender_type_id'])) {
|
||||
$entry['account_id'] = $this->LedgerEntry->Tender->TenderType->
|
||||
accountID($entry['Tender']['tender_type_id']);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
|
||||
if (isset($ids['transaction_id']))
|
||||
$ids['receipt_id'] = $ids['transaction_id'];
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addWaiver
|
||||
* - Adds a new waiver
|
||||
*/
|
||||
|
||||
function addWaiver($data, $customer_id, $lease_id = null) {
|
||||
$this->prEnter(compact('data', 'customer_id', 'lease_id'));
|
||||
// REVISIT <AP>: 20090802
|
||||
// Completely un-implemented. Just copied from addReceipt
|
||||
// and search-replace receipt with waiver.
|
||||
return array('error' => true);
|
||||
|
||||
// Establish the transaction as a waiver
|
||||
$waiver =& $data['Transaction'];
|
||||
$waiver['type'] = 'RECEIPT';
|
||||
$waiver['crdr'] = 'CREDIT';
|
||||
$waiver['account_id'] = $this->Account->accountReceivableAccountID();
|
||||
$waiver['customer_id'] = $customer_id;
|
||||
$waiver['lease_id'] = $lease_id;
|
||||
|
||||
// Go through the statement entries and flag as payments
|
||||
foreach ($data['Entry'] AS &$entry) {
|
||||
$entry['type'] = 'WAIVE';
|
||||
$entry['crdr'] = 'DEBIT';
|
||||
if (empty($entry['account_id']) && isset($entry['Tender']['tender_type_id'])) {
|
||||
$entry['account_id'] = $this->LedgerEntry->Tender->TenderType->
|
||||
accountID($entry['Tender']['tender_type_id']);
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
|
||||
if (isset($ids['transaction_id']))
|
||||
$ids['waiver_id'] = $ids['transaction_id'];
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addDeposit
|
||||
* - Adds a new bank deposit
|
||||
*/
|
||||
|
||||
function addDeposit($data, $account_id) {
|
||||
$this->prEnter(compact('data', 'account_id'));
|
||||
|
||||
// Establish the transaction as a deposit
|
||||
$deposit =& $data['Transaction'];
|
||||
$deposit['type'] = 'DEPOSIT';
|
||||
$deposit['crdr'] = 'DEBIT';
|
||||
$deposit['account_id'] = $account_id;
|
||||
$deposit['customer_id'] = null;
|
||||
$deposit['lease_id'] = null;
|
||||
|
||||
// Save the list of IDs, so that we can mark their
|
||||
// deposit transaction after it has been created.
|
||||
$tender_ids = array_map(create_function('$item', 'return $item["tender_id"];'),
|
||||
$data['Entry']);
|
||||
|
||||
// Go through the statement entries and re-group by account id
|
||||
$group = array();
|
||||
foreach ($data['Entry'] AS &$entry) {
|
||||
if (!isset($group[$entry['account_id']]))
|
||||
$group[$entry['account_id']] =
|
||||
array('account_id' => $entry['account_id'],
|
||||
'crdr' => 'CREDIT',
|
||||
'amount' => 0);
|
||||
$group[$entry['account_id']]['amount'] += $entry['amount'];
|
||||
}
|
||||
$data['Entry'] = $group;
|
||||
|
||||
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
|
||||
if (isset($ids['transaction_id']))
|
||||
$ids['deposit_id'] = $ids['transaction_id'];
|
||||
|
||||
if (!empty($ids['deposit_id'])) {
|
||||
$this->LedgerEntry->Tender->updateAll
|
||||
(array('Tender.deposit_transaction_id' => $ids['deposit_id']),
|
||||
array('Tender.id' => $tender_ids)
|
||||
);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addClose
|
||||
* - Adds a new transaction for closing ledgers
|
||||
*/
|
||||
|
||||
function addClose($data) {
|
||||
$this->prEnter(compact('data'));
|
||||
|
||||
// Establish the transaction as a close
|
||||
$close =& $data['Transaction'];
|
||||
$close['type'] = 'CLOSE';
|
||||
$close['account_id'] = null;
|
||||
$close['customer_id'] = null;
|
||||
$close['lease_id'] = null;
|
||||
|
||||
$ledger_ids = array();
|
||||
$data['Entry'] = array();
|
||||
foreach ($data['Ledger'] AS $ledger) {
|
||||
$ledger_id = $ledger['old_ledger_id'];
|
||||
$new_ledger_id = $ledger['new_ledger_id'];
|
||||
$amount = $ledger['amount'];
|
||||
$account_id = $this->Account->Ledger->accountID($ledger_id);
|
||||
$crdr = strtoupper($this->Account->fundamentalOpposite($account_id));
|
||||
$comment = "Ledger Carry Forward (c/f)";
|
||||
|
||||
// Save the ledger ID for later, to mark it as closed
|
||||
$ledger_ids[] = $ledger_id;
|
||||
|
||||
// No need to generate ledger entries if there is no balance
|
||||
if (empty($ledger['amount']) || $ledger['amount'] == 0)
|
||||
continue;
|
||||
|
||||
// Add an entry to carry the ledger balance forward
|
||||
$data['Entry'][] = compact('account_id', 'ledger_id', 'new_ledger_id',
|
||||
'crdr', 'amount', 'comment');
|
||||
}
|
||||
unset($data['Ledger']);
|
||||
|
||||
// Add the transaction and carry forward balances
|
||||
$ids = $this->addTransaction($data['Transaction'], $data['Entry']);
|
||||
if (isset($ids['transaction_id']))
|
||||
$ids['close_id'] = $ids['transaction_id'];
|
||||
|
||||
// Mark the older ledgers as closed
|
||||
if (!empty($ids['close_id'])) {
|
||||
$this->LedgerEntry->Ledger->updateAll
|
||||
(array('Ledger.close_transaction_id' => $ids['close_id']),
|
||||
array('Ledger.id' => $ledger_ids)
|
||||
);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: verifyTransaction
|
||||
* - Verifies consistenty of new transaction data
|
||||
* (not in a pre-existing transaction)
|
||||
*/
|
||||
function verifyTransaction($transaction, $entries) {
|
||||
//$this->prFunctionLevel(10);
|
||||
$this->prEnter(compact('transaction', 'entries'));
|
||||
|
||||
// Verify required Transaction data is present
|
||||
if (empty($transaction['type']) ||
|
||||
($transaction['type'] != 'CLOSE'
|
||||
&& (empty($transaction['account_id']) ||
|
||||
empty($transaction['crdr']))) ||
|
||||
(in_array($transaction['type'], array('INVOICE', 'RECEIPT'))
|
||||
&& empty($transaction['customer_id']))
|
||||
) {
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
|
||||
// Verify all entries
|
||||
foreach ($entries AS $entry) {
|
||||
// Ensure these items are null'ed out so we don't
|
||||
// accidentally pick up stale data.
|
||||
$le1 = $le1_tender = $le2 = $se = null;
|
||||
extract($entry);
|
||||
if (!empty($le1) && !empty($le2) &&
|
||||
!$this->LedgerEntry->DoubleEntry->verifyDoubleEntry($le1, $le2, $le1_tender)) {
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
if (!empty($se) &&
|
||||
!$this->StatementEntry->verifyStatementEntry($se)) {
|
||||
return $this->prReturn(false);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->prReturn(true);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addTransaction
|
||||
* - Adds a new transaction, and the appropriate ledger and statement
|
||||
* entries, as layed out in the $data['Entry'] array. The array is
|
||||
* overloaded, since it is used to create both ledger _and_ statement
|
||||
* entries.
|
||||
*
|
||||
* $data
|
||||
* - Transaction
|
||||
* - [MANDATORY]
|
||||
* - type (INVOICE, RECEIPT)
|
||||
* - account_id
|
||||
* - crdr
|
||||
* - [OPTIONAL]
|
||||
* - stamp
|
||||
* (default: NOW)
|
||||
* - comment
|
||||
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
||||
* - id
|
||||
* - amount
|
||||
* - customer_id
|
||||
* - ledger_id
|
||||
*
|
||||
* - Entry (array)
|
||||
* - [MANDATORY]
|
||||
* - type (CHARGE, PAYMENT)
|
||||
* - account_id
|
||||
* - crdr
|
||||
* - amount
|
||||
* - [OPTIONAL]
|
||||
* - effective_date
|
||||
* - through_date
|
||||
* - due_date
|
||||
* - comment (used for statement or ledger entry, based on context)
|
||||
* - ledger_entry_comment
|
||||
* - statement_entry_comment
|
||||
* - Tender
|
||||
* - [MANDATORY]
|
||||
* - tender_type_id
|
||||
* - [OPTIONAL]
|
||||
* - name
|
||||
* (default: Entry Account Name & data1)
|
||||
* - data1, data2, data3, data4
|
||||
* - comment
|
||||
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
||||
* - id
|
||||
* - ledger_entry_id
|
||||
* - deposit_transaction_id
|
||||
* - nsf_transaction_id
|
||||
* - [AUTOMATICALLY SET] (if set, these items will be overwritten)
|
||||
* - id
|
||||
* - transaction_id
|
||||
* - ledger_id
|
||||
*
|
||||
*/
|
||||
|
||||
function addTransaction($transaction, $entries, $subtype = null) {
|
||||
$this->prEnter(compact('transaction', 'entries', 'subtype'));
|
||||
|
||||
// Verify that we have a transaction and entries
|
||||
if (empty($transaction) ||
|
||||
($transaction['type'] !== 'CLOSE' && empty($entries)))
|
||||
return $this->prReturn(array('error' => true));
|
||||
|
||||
// set ledger ID as the current ledger of the specified account
|
||||
if (empty($transaction['ledger_id']))
|
||||
$transaction['ledger_id'] =
|
||||
$this->Account->currentLedgerID($transaction['account_id']);
|
||||
|
||||
// Automatically figure out the customer if we have the lease
|
||||
if (!empty($transaction['lease_id']) && empty($transaction['customer_id'])) {
|
||||
$L = new Lease();
|
||||
$L->recursive = -1;
|
||||
$lease = $L->read(null, $transaction['lease_id']);
|
||||
$transaction['customer_id'] = $lease['Lease']['customer_id'];
|
||||
}
|
||||
|
||||
// Break each entry out of the combined statement/ledger entry
|
||||
// and into individual entries appropriate for saving. While
|
||||
// we're at it, calculate the transaction total as well.
|
||||
$transaction['amount'] = 0;
|
||||
foreach ($entries AS &$entry) {
|
||||
// Ensure these items are null'ed out so we don't
|
||||
// accidentally pick up stale data.
|
||||
$le1 = $le1_tender = $le2 = $se = null;
|
||||
|
||||
// Add entry amount into the transaction total
|
||||
$transaction['amount'] += $entry['amount'];
|
||||
|
||||
// Set up our comments, possibly using the default 'comment' field
|
||||
if (empty($entry['ledger_entry_comment'])) {
|
||||
if ($transaction['type'] != 'INVOICE' && !empty($entry['comment']))
|
||||
$entry['ledger_entry_comment'] = $entry['comment'];
|
||||
else
|
||||
$entry['ledger_entry_comment'] = null;
|
||||
}
|
||||
if (empty($entry['statement_entry_comment'])) {
|
||||
if ($transaction['type'] == 'INVOICE' && !empty($entry['comment']))
|
||||
$entry['statement_entry_comment'] = $entry['comment'];
|
||||
else
|
||||
$entry['statement_entry_comment'] = null;
|
||||
}
|
||||
|
||||
// Create one half of the Double Ledger Entry (and the Tender)
|
||||
$le1 =
|
||||
array_intersect_key($entry,
|
||||
array_flip(array('ledger_id', 'account_id', 'crdr', 'amount')));
|
||||
$le1['comment'] = $entry['ledger_entry_comment'];
|
||||
$le1_tender = isset($entry['Tender']) ? $entry['Tender'] : null;
|
||||
|
||||
// Create the second half of the Double Ledger Entry
|
||||
if ($transaction['type'] == 'CLOSE') {
|
||||
$le2 =
|
||||
array_intersect_key($entry,
|
||||
array_flip(array('account_id', 'amount')));
|
||||
$le2['ledger_id'] = $entry['new_ledger_id'];
|
||||
$le2['crdr'] = strtoupper($this->Account->fundamentalOpposite($le1['crdr']));
|
||||
$le2['comment'] = "Ledger Balance Forward (b/f)";
|
||||
}
|
||||
else {
|
||||
$le2 =
|
||||
array_intersect_key($entry,
|
||||
array_flip(array('amount'))) +
|
||||
array_intersect_key($transaction,
|
||||
array_flip(array('ledger_id', 'account_id', 'crdr')));
|
||||
}
|
||||
|
||||
// Create the statement entry
|
||||
$se =
|
||||
array_intersect_key($entry,
|
||||
array_flip(array('type', 'amount',
|
||||
'effective_date', 'through_date', 'due_date',
|
||||
'customer_id', 'lease_id',
|
||||
'charge_entry_id'))) +
|
||||
array_intersect_key($transaction,
|
||||
array_flip(array('customer_id', 'lease_id')));
|
||||
$se['comment'] = $entry['statement_entry_comment'];
|
||||
|
||||
// (PAYMENTS will have statement entries created below, when
|
||||
// assigning credits, and DEPOSITS don't have statement entries)
|
||||
if ($transaction['type'] != 'INVOICE' && $subtype !== 'NSF')
|
||||
$se = null;
|
||||
|
||||
// NSF transactions don't use LedgerEntries
|
||||
// REVISIT <AP>: 20090731
|
||||
// Doesn't seem right... probably doing this because of the
|
||||
// single A/R entry we add below for NSF
|
||||
if ($subtype === 'NSF')
|
||||
$le1 = $le1_tender = $le2 = null;
|
||||
|
||||
// Replace combined entry with our new individual entries
|
||||
$entry = compact('le1', 'le1_tender', 'le2', 'se');
|
||||
}
|
||||
|
||||
if ($subtype === 'NSF') {
|
||||
// REVISIT <AP>: 20090731
|
||||
// Should we be doing this, or just doing individual ledger entries
|
||||
// that were created above before we nulled them out
|
||||
array_unshift($entries,
|
||||
array('le1' => array('account_id' => $transaction['account_id'],
|
||||
'crdr' => 'DEBIT',
|
||||
'amount' => $transaction['amount']),
|
||||
'le2' => array('account_id' => $this->Account->accountReceivableAccountID(),
|
||||
'crdr' => 'CREDIT',
|
||||
'amount' => $transaction['amount'])
|
||||
));
|
||||
}
|
||||
|
||||
$this->pr(20, compact('transaction', 'entries'));
|
||||
|
||||
// Move forward, verifying and saving everything.
|
||||
$ret = array();
|
||||
if (!$this->verifyTransaction($transaction, $entries))
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Save transaction to the database
|
||||
$this->create();
|
||||
if (!$this->save($transaction))
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Set up our return ids array
|
||||
$ret['transaction_id'] = $this->id;
|
||||
$ret['entries'] = array();
|
||||
$ret['error'] = false;
|
||||
|
||||
// Go through the entries
|
||||
foreach ($entries AS $e_index => &$entry) {
|
||||
// Ensure these items are null'ed out so we don't
|
||||
// accidentally pick up stale data.
|
||||
$le1 = $le1_tender = $le2 = $se = null;
|
||||
extract($entry);
|
||||
|
||||
if (!empty($le1) && !empty($le2)) {
|
||||
$le1['transaction_id'] = $le2['transaction_id'] = $ret['transaction_id'];
|
||||
if (isset($le1_tender))
|
||||
$le1_tender['customer_id'] = $transaction['customer_id'];
|
||||
$result = $this->LedgerEntry->DoubleEntry->addDoubleEntry($le1, $le2, $le1_tender);
|
||||
$ret['entries'][$e_index]['DoubleEntry'] = $result;
|
||||
if ($result['error']) {
|
||||
$ret['error'] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($se)) {
|
||||
//pr($ret['entries'][$e_index]['DoubleEntry']); die;
|
||||
$le_id = $ret['entries'][0]['DoubleEntry']['Entry1']['ledger_entry_id'];
|
||||
$se['transaction_id'] = $ret['transaction_id'];
|
||||
$se['Fraction'] = array(array('ledger_entry_id' => $le_id,
|
||||
'amount' => $se['amount']));
|
||||
$result = $this->StatementEntry->addStatementEntry($se);
|
||||
$ret['entries'][$e_index]['StatementEntry'] = $result;
|
||||
if ($result['error']) {
|
||||
$ret['error'] = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (($transaction['type'] == 'INVOICE' ||
|
||||
$transaction['type'] == 'RECEIPT') &&
|
||||
$subtype !== 'NSF' && !$ret['error']) {
|
||||
$result = $this->StatementEntry->assignCredits
|
||||
(array('link' => array('Customer'),
|
||||
'conditions' => array('Customer.id' => $transaction['customer_id'])),
|
||||
($transaction['type'] == 'RECEIPT'
|
||||
? $ret['transaction_id']
|
||||
: null));
|
||||
|
||||
$ret['assigned'] = $result;
|
||||
if ($result['error'])
|
||||
$ret['error'] = true;
|
||||
}
|
||||
|
||||
return $this->prReturn($ret);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: addNsf
|
||||
* - Adds NSF transaction
|
||||
*/
|
||||
|
||||
function addNsf($tender, $stamp) {
|
||||
$this->prEnter(compact('tender', 'stamp'));
|
||||
|
||||
$ret = array();
|
||||
|
||||
// Enter the NSF
|
||||
// This is the transaction pulling money from the bank account
|
||||
// and recording it in the NSF account. It has nothing to do
|
||||
// with the customer statement (charges, payments, credits, etc).
|
||||
$bounce_result = $this->addDeposit
|
||||
(array('Transaction' => array(),
|
||||
'Entry' => array(array('tender_id' => null,
|
||||
'account_id' => $this->Account->nsfAccountID(),
|
||||
'amount' => -1 * $tender['LedgerEntry']['amount'],
|
||||
))),
|
||||
$tender['Transaction']['account_id']);
|
||||
|
||||
$this->pr(20, compact('bounce_result'));
|
||||
$ret['bounce'] = $bounce_result;
|
||||
if ($bounce_result['error'])
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Since we may have saved the nsf transaction with a null
|
||||
// timestamp, query it back out of the database to find out
|
||||
// what timestamp was _really_ specified, for later use.
|
||||
$bounce = $this->find
|
||||
('first', array('contain' => false, 'id' => $bounce_result['transaction_id']));
|
||||
$this->pr(20, compact('bounce'));
|
||||
$stamp = $bounce['Transaction']['stamp'];
|
||||
|
||||
// OK, now move into customer realm, finding all statement
|
||||
// entries that were affected by the bad payment (tender).
|
||||
$nsf_ledger_entry = $this->LedgerEntry->find
|
||||
('first', array
|
||||
('contain' => array('Transaction' =>
|
||||
array(//'fields' => array(),
|
||||
'StatementEntry' =>
|
||||
array(//'fields' => array(),
|
||||
),
|
||||
),
|
||||
),
|
||||
'conditions' => array('LedgerEntry.id' => $tender['LedgerEntry']['id']),
|
||||
));
|
||||
|
||||
$this->pr(20, compact('nsf_ledger_entry'));
|
||||
if (!$nsf_ledger_entry)
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Build a transaction to adjust all of the statement entries
|
||||
$rollback = array('Transaction' => array(), 'Entry' => array());
|
||||
|
||||
$rollback['Transaction']['stamp'] = $stamp;
|
||||
$rollback['Transaction']['type'] = 'RECEIPT';
|
||||
$rollback['Transaction']['crdr'] = 'DEBIT'; // Unused... keeps verifyTx happy
|
||||
$rollback['Transaction']['account_id'] = $this->Account->nsfAccountID();
|
||||
$rollback['Transaction']['customer_id'] = $tender['Tender']['customer_id'];
|
||||
$rollback['Transaction']['amount'] = -1 * $tender['LedgerEntry']['amount'];
|
||||
|
||||
foreach ($nsf_ledger_entry['Transaction']['StatementEntry'] AS $payment) {
|
||||
if ($payment['type'] === 'SURPLUS') {
|
||||
$payment['type'] = 'VOID';
|
||||
$this->StatementEntry->id = $payment['id'];
|
||||
$this->StatementEntry->saveField('type', $payment['type']);
|
||||
}
|
||||
else {
|
||||
$rollback['Entry'][] =
|
||||
array('type' => $payment['type'],
|
||||
'amount' => -1 * $payment['amount'],
|
||||
'account_id' => $this->Account->nsfAccountID(),
|
||||
'customer_id' => $payment['customer_id'],
|
||||
'lease_id' => $payment['lease_id'],
|
||||
'charge_entry_id' => $payment['charge_entry_id'],
|
||||
'effective_date' => $stamp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Record the transaction, which will un-pay previously paid
|
||||
// charges, void any credits, and other similar work.
|
||||
$rollback_result = $this->addTransaction($rollback['Transaction'], $rollback['Entry'], 'NSF');
|
||||
$this->pr(20, compact('rollback', 'rollback_result'));
|
||||
$ret['rollback'] = $rollback_result;
|
||||
if ($rollback_result['error'])
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
// Add NSF Charge
|
||||
$charge_result = $this->addInvoice
|
||||
(array('Transaction' => compact('stamp'),
|
||||
|
||||
'Entry' =>
|
||||
array
|
||||
(array('account_id' => $this->Account->nsfChargeAccountID(),
|
||||
'effective_date' => $stamp,
|
||||
// REVISIT <AP>: 20090730
|
||||
// BAD, BAD, BAD... who would actually
|
||||
// hardcode a value like this???? ;-)
|
||||
'amount' => 35,
|
||||
'comment' => "NSF: " . $tender['Tender']['name'],
|
||||
),
|
||||
),
|
||||
),
|
||||
$tender['Tender']['customer_id']);
|
||||
|
||||
$this->pr(20, compact('charge_result'));
|
||||
$ret['charge'] = $charge_result;
|
||||
if ($charge_result['error'])
|
||||
return $this->prReturn(array('error' => true) + $ret);
|
||||
|
||||
$ret['nsf_transaction_id'] = $ret['bounce']['transaction_id'];
|
||||
$ret['nsf_ledger_entry_id'] = $ret['rollback']['entries'][0]['DoubleEntry']['Entry1']['ledger_entry_id'];
|
||||
return $this->prReturn($ret + array('error' => false));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: stats
|
||||
* - Returns summary data from the requested transaction
|
||||
*/
|
||||
function stats($id = null, $query = null, $balance_account_id = null) {
|
||||
$this->prEnter(compact('id', 'query'));
|
||||
|
||||
$this->queryInit($query);
|
||||
unset($query['group']);
|
||||
|
||||
if (isset($id)) {
|
||||
$query['conditions'][] = array('Transaction.id' => $id);
|
||||
$query['group'] = 'Transaction.id';
|
||||
}
|
||||
else
|
||||
// CakePHP seems to automagically add in our ID as a part
|
||||
// of the query conditions, but only on a 'first' query,
|
||||
// not an 'all'. I suppose this is helpful :-/
|
||||
unset($this->id);
|
||||
|
||||
if (empty($query['fields']))
|
||||
$query['fields'] = array();
|
||||
|
||||
$stats = array();
|
||||
foreach ($this->hasMany AS $table => $association) {
|
||||
// Only calculate stats for *Entry types
|
||||
if (!preg_match("/Entry$/", $table) &&
|
||||
!preg_match("/Entry$/", $association['className']))
|
||||
continue;
|
||||
|
||||
$squery = $query;
|
||||
$squery['link'][$table] = array('fields' => array());
|
||||
|
||||
if ($table == 'LedgerEntry') {
|
||||
if (isset($balance_account_id)) {
|
||||
$squery['link']['LedgerEntry']['Account'] = array('fields' => array());
|
||||
$squery['conditions'][] = array("Account.id" => $balance_account_id);
|
||||
}
|
||||
|
||||
$squery['fields'] = array_merge($squery['fields'],
|
||||
$this->LedgerEntry->debitCreditFields(true, $balance_account_id != null));
|
||||
}
|
||||
elseif ($table == 'StatementEntry') {
|
||||
$squery['fields'] = array_merge($squery['fields'],
|
||||
$this->StatementEntry->chargePaymentFields(true));
|
||||
}
|
||||
else {
|
||||
$squery['fields'][] = "SUM({$table}.amount) AS total";
|
||||
$squery['fields'][] = "COUNT({$table}.id) AS entries";
|
||||
}
|
||||
$stats[$table] = $this->find('first', $squery);
|
||||
// REVISIT <AP>: 20090724
|
||||
// [0][0] is for when we do an 'all' query. This can
|
||||
// be removed at some point, but I'm keeping it while
|
||||
// toggling between 'all' and 'first' (testing).
|
||||
if (isset($stats[$table][0][0]))
|
||||
$stats[$table] += $stats[$table][0][0];
|
||||
else
|
||||
$stats[$table] += $stats[$table][0];
|
||||
unset($stats[$table][0]);
|
||||
}
|
||||
|
||||
return $this->prReturn($stats);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,2 +0,0 @@
|
||||
1243395559
|
||||
a:36:{i:0;s:12:"pmgr_actions";i:1;s:27:"pmgr_actions_late_schedules";i:2;s:17:"pmgr_charge_types";i:3;s:19:"pmgr_config_options";i:4;s:18:"pmgr_config_system";i:5;s:20:"pmgr_config_versions";i:6;s:22:"pmgr_contact_addresses";i:7;s:19:"pmgr_contact_emails";i:8;s:20:"pmgr_contact_methods";i:9;s:19:"pmgr_contact_phones";i:10;s:13:"pmgr_contacts";i:11;s:18:"pmgr_group_options";i:12;s:22:"pmgr_group_permissions";i:13;s:11:"pmgr_groups";i:14;s:19:"pmgr_late_schedules";i:15;s:19:"pmgr_lease_contacts";i:16;s:16:"pmgr_lease_types";i:17;s:11:"pmgr_leases";i:18;s:14:"pmgr_map_units";i:19;s:9:"pmgr_maps";i:20;s:10:"pmgr_notes";i:21;s:18:"pmgr_payment_types";i:22;s:15:"pmgr_site_areas";i:23;s:21:"pmgr_site_memberships";i:24;s:17:"pmgr_site_options";i:25;s:10:"pmgr_sites";i:26;s:24:"pmgr_transaction_charges";i:27;s:25:"pmgr_transaction_payments";i:28;s:25:"pmgr_transaction_receipts";i:29;s:32:"pmgr_transaction_reconciliations";i:30;s:15:"pmgr_unit_sizes";i:31;s:15:"pmgr_unit_types";i:32;s:10:"pmgr_units";i:33;s:17:"pmgr_user_options";i:34;s:10:"pmgr_users";i:35;s:5:"posts";}
|
||||
@@ -24,21 +24,22 @@ function onGridLoadComplete() {
|
||||
}
|
||||
|
||||
function updateEntriesGrid() {
|
||||
var cust = new Array();
|
||||
|
||||
var account_ids = new Array();
|
||||
$("INPUT[type='checkbox']:checked").each(function(i) {
|
||||
account_ids.push($(this).val());
|
||||
});
|
||||
|
||||
cust['collected_account_id'] = <?php echo $account['id']; ?>;
|
||||
cust['collected_from_date'] = $('#TxFromDate').val();
|
||||
cust['collected_through_date'] = $('#TxThroughDate').val();
|
||||
cust['collected_payment_accounts'] = account_ids;
|
||||
var cust = new Array();
|
||||
cust['from_date'] = $('#TxFromDate').val();
|
||||
cust['through_date'] = $('#TxThroughDate').val();
|
||||
cust['account_id'] = account_ids;
|
||||
|
||||
var dynamic_post = new Array();
|
||||
dynamic_post['custom'] = cust;
|
||||
|
||||
$('#collected-total').html('Calculating...');
|
||||
$('#collected-entries-jqGrid').clearGridData();
|
||||
$('#collected-entries-jqGrid').setPostDataItem('custom', serialize(cust));
|
||||
$('#collected-entries-jqGrid').setPostDataItem('dynamic_post', serialize(dynamic_post));
|
||||
$('#collected-entries-jqGrid')
|
||||
.setGridParam({ page: 1 })
|
||||
.trigger("reloadGrid");
|
||||
@@ -153,11 +154,8 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
* Entries
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'collected_account_id' => $account['id'],
|
||||
|
||||
// Grid configuration
|
||||
echo $this->element('statement_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'grid_div_id' => 'collected-entries',
|
||||
@@ -165,7 +163,10 @@ echo $this->element('ledger_entries', array
|
||||
'grid_events' => array('loadComplete' => 'onGridLoadComplete()'),
|
||||
//'grid_setup' => array('hiddengrid' => true),
|
||||
//'caption' => '<SPAN id="receipt-charges-caption"></SPAN>',
|
||||
'caption' => 'Collected ' . Inflector::pluralize($account['name'])
|
||||
'caption' => 'Collected ' . Inflector::pluralize($account['name']),
|
||||
'filter' => array('StatementEntry.type' => 'PAYMENT',
|
||||
'ChargeEntry.account_id' => $account['id']),
|
||||
'exclude' => array('Account', 'Charge'),
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<H2>Deposit Slip: ' . date('l, F jS, Y, g:ia') . '</H2>' . "\n";
|
||||
|
||||
//pr(compact('deposit'));
|
||||
|
||||
// Handle account summaries
|
||||
$rows = array();
|
||||
$row_class = array();
|
||||
foreach ($deposit['ledgers'] AS $ledger) {
|
||||
$row_class[] = array();
|
||||
$rows[] = array($ledger['name'].':',
|
||||
FormatHelper::_n(count($ledger['entries']), 'Item'),
|
||||
FormatHelper::currency($ledger['total'], true));
|
||||
}
|
||||
$row_class[] = 'grand';
|
||||
$rows[] = array('Deposit Total:',
|
||||
null,
|
||||
FormatHelper::currency($deposit['total'], true));
|
||||
echo $this->element('table',
|
||||
array('class' => 'deposit-summary',
|
||||
'rows' => $rows,
|
||||
'row_class' => $row_class,
|
||||
'column_class' => array('account', 'quantity', 'total'),
|
||||
'suppress_alternate_rows' => true,
|
||||
));
|
||||
|
||||
|
||||
// Print out the items of each ledger
|
||||
foreach ($deposit['ledgers'] AS $ledger) {
|
||||
//echo ('Count: ' . count($ledger['entries']) . '<BR>');
|
||||
//pr($ledger['entries']);
|
||||
if (count($ledger['entries']) == 0)
|
||||
continue;
|
||||
|
||||
$rows = array();
|
||||
foreach ($ledger['entries'] AS $entry) {
|
||||
$rows[] = array($entry['Customer']['name'],
|
||||
$entry['MonetarySource']['name'],
|
||||
$entry['LedgerEntry']['amount']);
|
||||
}
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item deposit-slip list',
|
||||
'caption' => $ledger['name'] . ' Items',
|
||||
'rows' => $rows,
|
||||
'headers' => array('Customer', 'Item', 'Amount'),
|
||||
'column_class' => array('customer', 'item', 'amount'),
|
||||
));
|
||||
}
|
||||
|
||||
/* End page div */
|
||||
//echo '</div>' . "\n";
|
||||
@@ -9,12 +9,19 @@ echo '<div class="account view">' . "\n";
|
||||
* Account Detail Main Section
|
||||
*/
|
||||
|
||||
$rows = array(array('ID', $account['Account']['id']),
|
||||
array('Name', $account['Account']['name']),
|
||||
array('Type', $account['Account']['type']),
|
||||
array('External Name', $account['Account']['external_name']),
|
||||
array('External Account', $account['Account']['external_account']),
|
||||
array('Comment', $account['Account']['comment']));
|
||||
$ledger = $account['Ledger'];
|
||||
$current_ledger = $account['CurrentLedger'];
|
||||
|
||||
if (isset($account['Account']))
|
||||
$account = $account['Account'];
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array('ID', $account['id']);
|
||||
$rows[] = array('Name', $account['name']);
|
||||
$rows[] = array('Type', $account['type']);
|
||||
$rows[] = array('External Name', $account['external_name']);
|
||||
$rows[] = array('External Account', $account['external_account']);
|
||||
$rows[] = array('Comment', $account['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item account detail',
|
||||
@@ -56,9 +63,10 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
*/
|
||||
|
||||
echo $this->element('ledgers', array
|
||||
('config' => array
|
||||
('caption' => $account['Account']['name'] . " Ledgers",
|
||||
'rows' => $account['Ledger'],
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => $account['name'] . " Ledgers",
|
||||
'filter' => array('Account.id' => $account['id']),
|
||||
)));
|
||||
|
||||
|
||||
@@ -67,21 +75,36 @@ echo $this->element('ledgers', array
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'ledger_id' => $account['CurrentLedger']['id'],
|
||||
'account_type' => $account['Account']['type'],
|
||||
'group_by_tx' => true,
|
||||
|
||||
// Grid configuration
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' =>
|
||||
"Current Ledger: (" .
|
||||
"#{$account['Account']['id']}" .
|
||||
"-" .
|
||||
"{$account['CurrentLedger']['sequence']}" .
|
||||
")",
|
||||
),
|
||||
));
|
||||
('grid_div_id' => 'ledger-ledger-entry-list',
|
||||
'caption' => ("Current Ledger: " .
|
||||
"(". $current_ledger['name'] .")"),
|
||||
'filter' => array('Ledger.id' => $current_ledger['id']),
|
||||
'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance',
|
||||
empty($account['payments']) ? 'Tender' : null),
|
||||
'include' => array('Debit', 'Credit', 'Sub-Total'),
|
||||
)));
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Entire Account
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('grid_div_id' => 'account-ledger-entry-list',
|
||||
'grid_setup' => array('hiddengrid' => true),
|
||||
'caption' => "Entire Ledger",
|
||||
'filter' => array('Account.id' => $account['id']),
|
||||
'exclude' => array('Account', 'Amount', 'Cr/Dr', 'Balance',
|
||||
empty($account['payments']) ? 'Tender' : null),
|
||||
'include' => array('Debit', 'Credit', 'Sub-Total'),
|
||||
)));
|
||||
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
@@ -9,16 +9,24 @@ echo '<div class="contact view">' . "\n";
|
||||
* Contact Detail Main Section
|
||||
*/
|
||||
|
||||
$rows = array(array('First Name', $contact['Contact']['first_name']),
|
||||
array('Middle Name', $contact['Contact']['middle_name']),
|
||||
array('Last Name', $contact['Contact']['last_name']),
|
||||
array('Company', $contact['Contact']['company_name']),
|
||||
array('SSN', $contact['Contact']['id_federal']),
|
||||
array('ID', ($contact['Contact']['id_local']
|
||||
. ($contact['Contact']['id_local']
|
||||
? " - ".$contact['Contact']['id_local_state']
|
||||
: ""))),
|
||||
array('Comment', $contact['Contact']['comment']));
|
||||
$phones = $contact['ContactPhone'];
|
||||
$addresses = $contact['ContactAddress'];
|
||||
$emails = $contact['ContactEmail'];
|
||||
|
||||
if (isset($contact['Contact']))
|
||||
$contact = $contact['Contact'];
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array('First Name', $contact['first_name']);
|
||||
$rows[] = array('Middle Name', $contact['middle_name']);
|
||||
$rows[] = array('Last Name', $contact['last_name']);
|
||||
$rows[] = array('Company', $contact['company_name']);
|
||||
$rows[] = array('SSN', $contact['id_federal']);
|
||||
$rows[] = array('ID', ($contact['id_local']
|
||||
. ($contact['id_local']
|
||||
? " - ".$contact['id_local_state']
|
||||
: "")));
|
||||
$rows[] = array('Comment', $contact['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item contact detail',
|
||||
@@ -57,7 +65,7 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
*/
|
||||
$headers = array('Phone', 'Preference', 'Comment');
|
||||
$rows = array();
|
||||
foreach($contact['ContactPhone'] AS $phone) {
|
||||
foreach($phones AS $phone) {
|
||||
$rows[] = array(FormatHelper::phone($phone['phone']) .
|
||||
($phone['ext'] ? " x".$phone['ext'] : ""),
|
||||
$phone['ContactsMethod']['preference'] . " / " .
|
||||
@@ -79,7 +87,7 @@ echo $this->element('table',
|
||||
*/
|
||||
$headers = array('Address', 'Preference', 'Comment');
|
||||
$rows = array();
|
||||
foreach($contact['ContactAddress'] AS $address) {
|
||||
foreach($addresses AS $address) {
|
||||
$rows[] = array(preg_replace("/\n/", "<BR>\n", $address['address']) . "<BR>\n" .
|
||||
$address['city'] . ", " .
|
||||
$address['state'] . " " .
|
||||
@@ -103,7 +111,7 @@ echo $this->element('table',
|
||||
*/
|
||||
$headers = array('Email', 'Preference', 'Comment');
|
||||
$rows = array();
|
||||
foreach($contact['ContactEmail'] AS $email) {
|
||||
foreach($emails AS $email) {
|
||||
$rows[] = array($email['email'],
|
||||
$email['ContactsMethod']['preference'] . " / " .
|
||||
$email['ContactsMethod']['type'],
|
||||
@@ -123,9 +131,10 @@ echo $this->element('table',
|
||||
*/
|
||||
|
||||
echo $this->element('customers', array
|
||||
('config' => array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Related Customers',
|
||||
'rows' => $contact['Customer'],
|
||||
'filter' => array('Contact.id' => $contact['id']),
|
||||
)));
|
||||
|
||||
|
||||
|
||||
@@ -147,8 +147,13 @@ function addPaymentSource(flash) {
|
||||
|
||||
'<DIV ID="payment-div-%{id}">' +
|
||||
<?php
|
||||
// Rearrange to be of the form (id => name)
|
||||
$radioOptions = array();
|
||||
foreach ($paymentTypes AS $type)
|
||||
$radioOptions[$type['TenderType']['id']] = $type['TenderType']['name'];
|
||||
|
||||
echo FormatHelper::phpVarToJavascript
|
||||
($form->input('LedgerEntry.%{id}.account_id',
|
||||
($form->input('Entry.%{id}.tender_type_id',
|
||||
array('type' => 'radio',
|
||||
'separator' => '<BR>',
|
||||
'onclick' => ('switchPaymentType(' .
|
||||
@@ -160,73 +165,48 @@ function addPaymentSource(flash) {
|
||||
''
|
||||
),
|
||||
'legend' => false,
|
||||
'value' => $defaultAccount,
|
||||
'options' => $paymentAccounts))) . "+\n";
|
||||
'value' => $defaultType,
|
||||
'options' => $radioOptions))) . "+\n";
|
||||
?>
|
||||
'</DIV>' +
|
||||
|
||||
'<DIV ID="payment-amount-div-%{id}" CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-amount-%{id}">Amount</LABEL>' +
|
||||
' <INPUT TYPE="text" SIZE="20"' +
|
||||
' NAME="data[LedgerEntry][%{id}][amount]"' +
|
||||
' NAME="data[Entry][%{id}][amount]"' +
|
||||
' CLASS="payment"' +
|
||||
' ID="payment-amount-%{id}" />' +
|
||||
' <LABEL CLASS="payment" FOR="payment-amount-%{id}">Amount</LABEL>' +
|
||||
'</DIV>' +
|
||||
|
||||
<?php
|
||||
foreach ($paymentAccounts AS $account_id => $name) {
|
||||
foreach ($paymentTypes AS $type) {
|
||||
$type = $type['TenderType'];
|
||||
$div = '<DIV';
|
||||
$div .= ' ID="payment-type-div-%{id}-'.$account_id.'"';
|
||||
$div .= ' ID="payment-type-div-%{id}-'.$type['id'].'"';
|
||||
$div .= ' CLASS="payment-type-div-%{id}"';
|
||||
$div .= ' STYLE="display:none;">';
|
||||
if ($type['id'] != $defaultType)
|
||||
$div .= ' STYLE="display:none;"';
|
||||
$div .= '>';
|
||||
|
||||
if ($name == 'Check') {
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-check-number-%{id}">Check Number</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data1]"';
|
||||
$div .= ' ID="payment-check-number-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
}
|
||||
elseif ($name == 'Money Order') {
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-moneyorder-number-%{id}">Money Order Number</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data1]"';
|
||||
$div .= ' ID="payment-moneyorder-number-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
}
|
||||
elseif ($name == 'ACH') {
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-ach-routing-%{id}">Routing Number</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="9" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data1]"';
|
||||
$div .= ' ID="payment-ach-routing-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
$div .= ' <INPUT TYPE="hidden"';
|
||||
$div .= ' NAME="data[Entry][%{id}][type]['.$type['id'].'][tender_type_id]"';
|
||||
$div .= ' VALUE="'.$type['id'].'"';
|
||||
$div .= '>';
|
||||
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-ach-account-%{id}">Account Number</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="17" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data2]"';
|
||||
$div .= ' ID="payment-ach-account-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
}
|
||||
elseif ($name == 'Credit Card') {
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-creditcard-account-%{id}">Account Number</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="16" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data1]"';
|
||||
$div .= ' ID="payment-creditcard-account-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-creditcard-expiration-%{id}">Expiration Date</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="10" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data2]"';
|
||||
$div .= ' ID="payment-creditcard-expiration-%{id}" />';
|
||||
$div .= ' </DIV>';
|
||||
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <LABEL FOR="payment-creditcard-cvv2-%{id}">CVV2 Code</LABEL>';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="10" NAME="data[LedgerEntry][%{id}][acct]['.$account_id.'][data3]"';
|
||||
$div .= ' ID="payment-creditcard-cvv2-%{id}" />';
|
||||
$div .= '</DIV>';
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
for ($i=1; $i<=4; ++$i) {
|
||||
if (!empty($type["data{$i}_name"])) {
|
||||
$div .= '<DIV CLASS="input text required">';
|
||||
$div .= ' <INPUT TYPE="text" SIZE="20"';
|
||||
$div .= ' NAME="data[Entry][%{id}][type]['.$type['id'].'][data'.$i.']"';
|
||||
$div .= ' CLASS="payment"';
|
||||
$div .= ' ID="payment-data'.$i.'-%{id}" />';
|
||||
$div .= ' <LABEL';
|
||||
$div .= ' CLASS="payment"';
|
||||
$div .= ' FOR="payment-data'.$i.'-%{id}">';
|
||||
$div .= $type["data{$i}_name"];
|
||||
$div .= ' </LABEL>';
|
||||
$div .= '</DIV>';
|
||||
}
|
||||
}
|
||||
|
||||
$div .= '</DIV>';
|
||||
@@ -240,8 +220,8 @@ function addPaymentSource(flash) {
|
||||
|
||||
function switchPaymentType(paymentid_base, paymentid, radioid) {
|
||||
$("."+paymentid_base+"-"+paymentid).slideUp();
|
||||
var account_id = $("#"+radioid).val();
|
||||
$("#"+paymentid_base+"-"+paymentid+"-"+account_id).slideDown();
|
||||
var type_id = $("#"+radioid).val();
|
||||
$("#"+paymentid_base+"-"+paymentid+"-"+type_id).slideDown();
|
||||
}
|
||||
|
||||
|
||||
@@ -321,7 +301,7 @@ echo ('<DIV CLASS="receipt grid-selection-text">' .
|
||||
'</DIV>' . "\n");
|
||||
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
echo $this->element('statement_entries', array
|
||||
(// Element configuration
|
||||
'account_ftype' => 'credit',
|
||||
'limit' => 8,
|
||||
@@ -332,7 +312,7 @@ echo $this->element('ledger_entries', array
|
||||
'grid_div_id' => 'charge-entries',
|
||||
'grid_div_class' => 'text-below',
|
||||
'caption' => '<SPAN id="receipt-charges-caption"></SPAN>',
|
||||
'rows' => $charges['entry'],
|
||||
'rows' => $charges,
|
||||
),
|
||||
));
|
||||
|
||||
@@ -411,7 +391,7 @@ echo $form->submit('Generate Receipt') . "\n";
|
||||
"<?php echo $customer['id']; ?>" +
|
||||
'</A>');
|
||||
$("#receipt-customer-name").html("<?php echo $customer['name']; ?>");
|
||||
$("#receipt-balance").html(fmtCurrency("<?php echo $charges['balance']; ?>"));
|
||||
$("#receipt-balance").html(fmtCurrency("<?php echo $stats['balance']; ?>"));
|
||||
onGridState(null, 'hidden');
|
||||
<?php else: ?>
|
||||
onGridState(null, 'visible');
|
||||
|
||||
@@ -9,8 +9,9 @@ echo '<div class="customer view">' . "\n";
|
||||
* Customer Detail Main Section
|
||||
*/
|
||||
|
||||
$rows = array(array('Name', $customer['Customer']['name']),
|
||||
array('Comment', $customer['Customer']['comment']));
|
||||
$rows = array();
|
||||
$rows[] = array('Name', $customer['Customer']['name']);
|
||||
$rows[] = array('Comment', $customer['Customer']['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item customer detail',
|
||||
@@ -51,9 +52,11 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
*/
|
||||
|
||||
echo $this->element('contacts', array
|
||||
('config' => array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Customer Contacts',
|
||||
'rows' => $customer['Contact'],
|
||||
'filter' => array('Customer.id' => $customer['Customer']['id']),
|
||||
'include' => array('Type', 'Active'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -62,9 +65,11 @@ echo $this->element('contacts', array
|
||||
*/
|
||||
|
||||
echo $this->element('leases', array
|
||||
('config' => array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Lease History',
|
||||
'rows' => $customer['Lease'],
|
||||
'filter' => array('Customer.id' => $customer['Customer']['id']),
|
||||
'exclude' => array('Customer'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -72,16 +77,39 @@ echo $this->element('leases', array
|
||||
* Customer Account History
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'customer_id' => $customer['Customer']['id'],
|
||||
'ar_account' => true,
|
||||
|
||||
// Grid configuration
|
||||
echo $this->element('statement_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Account',
|
||||
),
|
||||
));
|
||||
'filter' => array('Customer.id' => $customer['Customer']['id'],
|
||||
'type !=' => 'VOID'),
|
||||
'exclude' => array('Customer'),
|
||||
)));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Customer Ledger History
|
||||
*/
|
||||
|
||||
/*
|
||||
* REVISIT <AP>: 20090724
|
||||
* It's not my intention to really include this, as I believe it
|
||||
* just will confuse folks. However, I've added it at the moment
|
||||
* to help me see the picture of what's happening. It may prove
|
||||
* useful with respect to identifying pre-payments, so after using
|
||||
* it for a while, maybe we can get a feeling for that. I suspect
|
||||
* it will be MUCH more useful just to add the pre-pay amount to
|
||||
* the info box, or provide a list of ledger entries that are JUST
|
||||
* pre-payments. We'll see...
|
||||
*/
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Ledger Entries',
|
||||
'filter' => array('Customer.id' => $customer['Customer']['id'],
|
||||
'Account.id !=' => '-AR-'),
|
||||
'exclude' => array('Customer'),
|
||||
)));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
|
||||
162
site/views/double_entries/view.ctp
Normal file
162
site/views/double_entries/view.ctp
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<div class="ledger-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"). This, when we provide
|
||||
// reconcile information, we're really providing reconcile info
|
||||
// for two independent accounts. The reconciling entries,
|
||||
// therefore, are those on the opposite side of the ledger in
|
||||
// each account. For example, assume this "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 ENTRY
|
||||
// | | | | |
|
||||
// | | | |75 75| <-- Deposit includes this entry
|
||||
// | | | | |
|
||||
//
|
||||
// In this case, we're looking to provide reconcile information
|
||||
// of A/R for (the credit side of) this entry, and also of Cash
|
||||
// (for the debit side). Taking the accounts as individual
|
||||
// entries, instead of the "double entry" representation in the
|
||||
// database, we're actually providing information on the two
|
||||
// A/R entries, 50 & 5, which are both debits, i.e. opposite
|
||||
// entries to the credit of A/R. The cash account entry
|
||||
// reconciles against the credit of 75. Again, this is the
|
||||
// opposite entry to the debit of Cash.
|
||||
//
|
||||
// Thus, for our debit_ledger_id, we're reconciling against
|
||||
// credits, and for our credit_ledger_id, against debits.
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* LedgerEntry Detail Main Section
|
||||
*/
|
||||
|
||||
$transaction = $entry['Transaction'];
|
||||
$ledgers = array('debit' => $entry['DebitLedger'],
|
||||
'credit' => $entry['CreditLedger']);
|
||||
$entries = array('debit' => $entry['DebitEntry'],
|
||||
'credit' => $entry['CreditEntry']);
|
||||
$customer = $entry['Customer'];
|
||||
$lease = $entry['Lease'];
|
||||
$entry = $entry['DoubleEntry'];
|
||||
|
||||
$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($entry['effective_date']));
|
||||
//$rows[] = array('Through', FormatHelper::date($entry['through_date']));
|
||||
$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 ledger-entry detail',
|
||||
'caption' => 'Double Ledger Entry Detail',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* LedgerEntry Info Box
|
||||
*/
|
||||
|
||||
/* 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"; */
|
||||
|
||||
/* $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 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',
|
||||
$ledger['Account']['id'])));
|
||||
$rows[] = array('Ledger', $html->link('#' . $ledger['Account']['id']
|
||||
. '-' . $ledger['sequence'],
|
||||
array('controller' => 'ledgers',
|
||||
'action' => 'view',
|
||||
$ledger['id'])));
|
||||
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
|
||||
$rows[] = array('Effect', $ledger['Account']['ftype'] == $type ? 'INCREASE' : 'DECREASE');
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => array('item', $type, 'detail'),
|
||||
'caption' => ucfirst($type) . ' Ledger Entry',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
}
|
||||
echo ('</DIV>' . "\n");
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* Supporting Elements Section
|
||||
*/
|
||||
|
||||
echo '<div CLASS="detail supporting">' . "\n";
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
/* End page div */
|
||||
echo '</div>' . "\n";
|
||||
@@ -4,20 +4,18 @@
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'Account.id', 'formatter' => 'id');
|
||||
$cols['Name'] = array('index' => 'Account.name', 'formatter' => 'longname');
|
||||
$cols['Type'] = array('index' => 'Account.type', 'formatter' => 'name');
|
||||
$cols['Entries'] = array('index' => 'entries', 'width' => '60', 'align' => 'right');
|
||||
$cols['Type'] = array('index' => 'Account.type', 'formatter' => 'enum');
|
||||
$cols['Entries'] = array('index' => 'entries', 'formatter' => 'number');
|
||||
$cols['Debits'] = array('index' => 'debits', 'formatter' => 'currency');
|
||||
$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Comment'] = array('index' => 'Account.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Name'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Name')
|
||||
->defaultFields(array('ID', 'Name'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Name'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Comment')));
|
||||
|
||||
@@ -2,23 +2,19 @@
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'Contact.id', 'formatter' => 'id');
|
||||
$cols['Last Name'] = array('index' => 'Contact.last_name', 'formatter' => 'name');
|
||||
$cols['First Name'] = array('index' => 'Contact.first_name', 'formatter' => 'name');
|
||||
$cols['Company'] = array('index' => 'Contact.company_name', 'formatter' => 'longname');
|
||||
if (0) { // REVISIT<AP>: Need to figure out how to put this in play
|
||||
$cols['Type'] = array('index' => 'ContactsCustomer.type', 'width' => '75');
|
||||
$cols['Active'] = array('index' => 'ContactsCustomer.active', 'width' => '75');
|
||||
}
|
||||
$cols['Comment'] = array('index' => 'Contact.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Last Name', 'First Name'));
|
||||
$cols['ID'] = array('index' => 'Contact.id', 'formatter' => 'id');
|
||||
$cols['Last Name'] = array('index' => 'Contact.last_name', 'formatter' => 'name');
|
||||
$cols['First Name'] = array('index' => 'Contact.first_name', 'formatter' => 'name');
|
||||
$cols['Company'] = array('index' => 'Contact.company_name', 'formatter' => 'longname');
|
||||
$cols['Type'] = array('index' => 'ContactsCustomer.type', 'formatter' => 'enum');
|
||||
$cols['Active'] = array('index' => 'ContactsCustomer.active', 'formatter' => 'enum');
|
||||
$cols['Comment'] = array('index' => 'Contact.comment', 'formatter' => 'comment');
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Last Name')
|
||||
->defaultFields(array('ID', 'Last Name', 'First Name'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Last Name', 'First Name', 'Company'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Type', 'Active', 'Comment')));
|
||||
|
||||
@@ -2,23 +2,25 @@
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'Customer.id', 'formatter' => 'id');
|
||||
if (0) // REVISIT<AP>: Need to figure out how to put this in play
|
||||
$cols['Relationship'] = array('index' => 'ContactsCustomer.type', 'width' => '75');
|
||||
$cols['Name'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Last Name'] = array('index' => 'PrimaryContact.last_name', 'formatter' => 'name');
|
||||
$cols['First Name'] = array('index' => 'PrimaryContact.first_name', 'formatter' => 'name');
|
||||
$cols['Leases'] = array('index' => 'lease_count', 'width' => '60');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Comment'] = array('index' => 'Customer.comment', 'formatter' => 'comment');
|
||||
$cols['ID'] = array('index' => 'Customer.id', 'formatter' => 'id');
|
||||
$cols['Relationship'] = array('index' => 'ContactsCustomer.type', 'formatter' => 'enum');
|
||||
$cols['Name'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Last Name'] = array('index' => 'PrimaryContact.last_name', 'formatter' => 'name');
|
||||
$cols['First Name'] = array('index' => 'PrimaryContact.first_name', 'formatter' => 'name');
|
||||
$cols['Leases'] = array('index' => 'lease_count', 'formatter' => 'number');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Comment'] = array('index' => 'Customer.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Last Name', 'First Name'));
|
||||
|
||||
// Certain fields are only valid with a particular context
|
||||
if (!isset($config['filter']['Contact.id']))
|
||||
$grid->invalidFields('Relationship');
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Name')
|
||||
->defaultFields(array('ID', 'Name'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Name', 'Last Name', 'First Name'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Comment')));
|
||||
|
||||
116
site/views/elements/double_entries.ctp
Normal file
116
site/views/elements/double_entries.ctp
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
|
||||
$cols['Entry'] = array('index' => 'LedgerEntry.id', 'formatter' => 'id');
|
||||
|
||||
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
|
||||
$cols['Effective'] = array('index' => 'LedgerEntry.effective_date', 'formatter' => 'date');
|
||||
$cols['Through'] = array('index' => 'LedgerEntry.through_date', 'formatter' => 'date');
|
||||
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name');
|
||||
$cols['Debit Account'] = array('index' => 'DebitAccount.name', 'formatter' => 'name');
|
||||
$cols['Credit Account'] = array('index' => 'CreditAccount.name', 'formatter' => 'name');
|
||||
|
||||
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id');
|
||||
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'name');
|
||||
|
||||
$cols['Source'] = array('index' => 'MonetarySource.name', 'formatter' => 'name');
|
||||
$cols['Comment'] = array('index' => 'LedgerEntry.comment', 'formatter' => 'comment', 'width'=>150);
|
||||
|
||||
$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency');
|
||||
$cols['Debit'] = array('index' => 'debit', 'formatter' => 'currency');
|
||||
$cols['Credit'] = array('index' => 'credit', 'formatter' => 'currency');
|
||||
|
||||
$cols['Last Payment'] = array('index' => 'last_paid', 'formatter' => 'date');
|
||||
$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency');
|
||||
$cols['Sub-Total'] = array('index' => 'subtotal-LedgerEntry.amount', 'formatter' => 'currency', 'sortable' => false);
|
||||
|
||||
|
||||
if (isset($transaction_id) || isset($reconcile_id))
|
||||
$grid->invalidFields('Transaction');
|
||||
|
||||
if (!isset($collected_account_id))
|
||||
$grid->invalidFields('Last Payment');
|
||||
|
||||
if (isset($account_ftype) || isset($ledger_id) || isset($account_id) || isset($ar_account) || isset($customer_id))
|
||||
$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) || isset($customer_id)) {
|
||||
$grid->invalidFields('Amount');
|
||||
$cols['Sub-Total']['index'] = 'subtotal-balance';
|
||||
} else {
|
||||
$grid->invalidFields(array('Debit', 'Credit'));
|
||||
$cols['Sub-Total']['index'] = 'subtotal-LedgerEntry.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))
|
||||
$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', 'Debit Account', 'Credit Account',
|
||||
'Customer', 'Unit',
|
||||
'Comment',
|
||||
'Amount', 'Debit', 'Credit',
|
||||
'Applied', 'Sub-Total')
|
||||
);
|
||||
@@ -62,63 +62,70 @@ $javascript->link('pmgr_jqGrid', false);
|
||||
// we'll just pass the desired fields to the controller
|
||||
// as part of the data fetch.
|
||||
$url = $html->url(array('controller' => $controller,
|
||||
'action' => 'jqGridData',
|
||||
'action' => 'gridData',
|
||||
'debug' => 0,
|
||||
));
|
||||
|
||||
// Create extra parameters that jqGrid will pass to our
|
||||
// controller whenever data is requested.
|
||||
// 'fields' will allow the controller to return only the
|
||||
// requested fields, and in the right order. Since fields
|
||||
// is a complex structure (an array), we'll need to
|
||||
// serialize it first for transport over HTTP.
|
||||
// requested fields, and in the right order.
|
||||
$postData = array();
|
||||
$postData['fields'] = serialize(array_map(create_function('$col',
|
||||
'return $col["index"];'),
|
||||
array_values($jqGridColumns)));
|
||||
$postData['fields'] = array_map(create_function('$col',
|
||||
'return $col["index"];'),
|
||||
array_values($jqGridColumns));
|
||||
|
||||
// Determine if we're to be using a custom list, or if
|
||||
// the data will simply be action based.
|
||||
if (isset($custom_ids)) {
|
||||
if (!isset($action))
|
||||
$action = 'idlist';
|
||||
$postData['idlist'] = serialize($custom_ids);
|
||||
}
|
||||
elseif (!isset($action)) {
|
||||
$action = null;
|
||||
$postData['idlist'] = $custom_ids;
|
||||
}
|
||||
|
||||
if (isset($custom_post_data)) {
|
||||
$postData['custom'] = serialize($custom_post_data);
|
||||
}
|
||||
|
||||
// 'action' will ensure that the controller provides the
|
||||
// correct subset of data
|
||||
$postData['action'] = $action;
|
||||
|
||||
if (isset($nolinks)) {
|
||||
if (isset($nolinks))
|
||||
$postData['nolinks'] = true;
|
||||
}
|
||||
|
||||
// 'action' will ensure that the controller does the right thing
|
||||
// 'filter' allows the app controller to automagic filter
|
||||
// 'custom' is for use solely by derived controllers
|
||||
$postData['action'] = isset($action) ? $action : null;
|
||||
$postData['filter'] = isset($filter) ? $filter : null;
|
||||
$postData['custom'] = isset($custom_post_data) ? $custom_post_data : null;
|
||||
|
||||
|
||||
// Perform column customizations.
|
||||
// This will largely be based off of the 'formatter' parameter,
|
||||
// but could be on any pertinent condition.
|
||||
foreach ($jqGridColumns AS &$col) {
|
||||
foreach ($jqGridColumns AS $header => &$col) {
|
||||
$default = array();
|
||||
|
||||
// Make sure every column has a name
|
||||
$default['name'] = preg_replace("/\./", '-', $col['index']);
|
||||
$default['name'] = preg_replace("/\./", '-', $col['index']);
|
||||
$default['force'] = isset($col['forcewidth']) ? $col['forcewidth'] : null;
|
||||
|
||||
// Perform customization based on formatter
|
||||
if (isset($col['formatter'])) {
|
||||
if ($col['formatter'] === 'id') {
|
||||
// Switch currency over to our own custom formatting
|
||||
// Use our custom formatting for ids
|
||||
$col['formatter'] = array('--special' => 'idFormatter');
|
||||
$default['width'] = 50;
|
||||
$default['align'] = 'center';
|
||||
|
||||
// For IDs, force the width by default,
|
||||
// unless otherwise instructed NOT to.
|
||||
if (!isset($default['force']))
|
||||
$default['force'] = true;
|
||||
}
|
||||
elseif ($col['formatter'] === 'number') {
|
||||
$default['width'] = 60;
|
||||
$default['align'] = 'right';
|
||||
|
||||
// No special formatting for number
|
||||
unset($col['formatter']);
|
||||
}
|
||||
elseif ($col['formatter'] === 'currency') {
|
||||
// Switch currency over to our own custom formatting
|
||||
// Use our custom formatting for currency
|
||||
$col['formatter'] = array('--special' => 'currencyFormatter');
|
||||
$default['width'] = 85;
|
||||
$default['align'] = 'right';
|
||||
@@ -128,21 +135,38 @@ foreach ($jqGridColumns AS &$col) {
|
||||
$default['width'] = 95;
|
||||
$default['align'] = 'center';
|
||||
}
|
||||
elseif ($col['formatter'] === 'name' || $col['formatter'] === 'longname') {
|
||||
elseif (preg_match("/^(long|short)?name$/",
|
||||
$col['formatter'], $matches)) {
|
||||
$default['width'] = 100;
|
||||
if ($col['formatter'] === 'longname')
|
||||
if (!empty($matches[1]) && $matches[1] === 'long')
|
||||
$default['width'] *= 1.5;
|
||||
if (!empty($matches[1]) && $matches[1] === 'short')
|
||||
$default['width'] *= 0.7;
|
||||
|
||||
// No special formatting for name
|
||||
unset($col['formatter']);
|
||||
}
|
||||
elseif ($col['formatter'] === 'enum') {
|
||||
$default['width'] = 60;
|
||||
//$default['align'] = 'right';
|
||||
|
||||
// No special formatting for enum
|
||||
unset($col['formatter']);
|
||||
}
|
||||
elseif ($col['formatter'] === 'comment') {
|
||||
$default['width'] = 300;
|
||||
$default['width'] = 150;
|
||||
$default['sortable'] = false;
|
||||
|
||||
// No special formatting for comment
|
||||
unset($col['formatter']);
|
||||
}
|
||||
// else just let the formatter pass through untouched
|
||||
|
||||
// Just a rough approximation to ensure columns
|
||||
// are wide enough to fully display their header.
|
||||
$min_width = strlen($header) * 10;
|
||||
if ((!isset($default['width']) || $default['width'] < $min_width) && !$default['force'])
|
||||
$default['width'] = $min_width;
|
||||
}
|
||||
|
||||
$col = array_merge($default, $col);
|
||||
@@ -165,7 +189,7 @@ if (isset($sort_order)) {
|
||||
}
|
||||
|
||||
if (1) { // debug
|
||||
$caption .= ' :: <span id="'.$grid_id.'-query"></span>';
|
||||
$caption .= '<span class="debug grid-query"> :: <span id="'.$grid_id.'-query"></span></span>';
|
||||
}
|
||||
|
||||
foreach (array_merge(array('loadComplete' => '', 'loadError' => ''),
|
||||
@@ -196,7 +220,9 @@ $jqGrid_setup = array_merge
|
||||
(array('mtype' => 'GET',
|
||||
'datatype' => 'xml',
|
||||
'url' => $url,
|
||||
'postData' => $postData,
|
||||
// Since postData is a complex structure (an array), we'll
|
||||
// need to serialize it first for transport over HTTP.
|
||||
'postData' => array('post' => serialize($postData)),
|
||||
'colNames' => array_keys($jqGridColumns),
|
||||
'colModel' => array('--special' => $jqGridColumns),
|
||||
'height' => $height,
|
||||
|
||||
@@ -14,13 +14,11 @@ $cols['Move-Out'] = array('index' => 'Lease.moveout_date', 'formatter' => 'dat
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Comment'] = array('index' => 'Lease.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Customer', 'Unit'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('LeaseID')
|
||||
->defaultFields(array('LeaseID', 'Lease'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Customer', 'Unit'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Comment')));
|
||||
|
||||
@@ -4,136 +4,27 @@
|
||||
$cols = array();
|
||||
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
|
||||
$cols['Entry'] = array('index' => 'LedgerEntry.id', 'formatter' => 'id');
|
||||
|
||||
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
|
||||
$cols['Effective'] = array('index' => 'LedgerEntry.effective_date', 'formatter' => 'date');
|
||||
$cols['Through'] = array('index' => 'LedgerEntry.through_date', 'formatter' => 'date');
|
||||
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name');
|
||||
$cols['Debit Account'] = array('index' => 'DebitAccount.name', 'formatter' => 'name');
|
||||
$cols['Credit Account'] = array('index' => 'CreditAccount.name', 'formatter' => 'name');
|
||||
|
||||
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id');
|
||||
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'name');
|
||||
|
||||
$cols['Source'] = array('index' => 'MonetarySource.name', 'formatter' => 'name');
|
||||
$cols['Cr/Dr'] = array('index' => 'LedgerEntry.crdr', 'formatter' => 'enum');
|
||||
$cols['Tender'] = array('index' => 'Tender.name', 'formatter' => 'longname');
|
||||
$cols['Comment'] = array('index' => 'LedgerEntry.comment', 'formatter' => 'comment', 'width'=>150);
|
||||
|
||||
$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency');
|
||||
$cols['Debit'] = array('index' => 'debit', 'formatter' => 'currency');
|
||||
$cols['Credit'] = array('index' => 'credit', 'formatter' => 'currency');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Sub-Total'] = array('index' => 'subtotal-balance', 'formatter' => 'currency', 'sortable' => false);
|
||||
|
||||
$cols['Last Payment'] = array('index' => 'last_paid', 'formatter' => 'date');
|
||||
$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency');
|
||||
$cols['Sub-Total'] = array('index' => 'subtotal-LedgerEntry.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-LedgerEntry.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)) {
|
||||
$grid->customData(compact('reconcile_id'))->limit(20);
|
||||
}
|
||||
|
||||
if (isset($collected_account_id)) {
|
||||
$config['action'] = 'collected';
|
||||
$grid->customData(compact('collected_account_id'))->limit(50);
|
||||
$grid->sortField('Last Payment');
|
||||
}
|
||||
|
||||
// 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
|
||||
->limit(50)
|
||||
->columns($cols)
|
||||
->sortField('Date')
|
||||
->defaultFields(array('Entry', 'Date', 'Amount'))
|
||||
->searchFields(array('Customer', 'Unit'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array('Transaction', 'Entry', 'Date', 'Effective', 'Last Payment',
|
||||
'Account', 'Debit Account', 'Credit Account',
|
||||
'Customer', 'Unit',
|
||||
'Comment',
|
||||
'Amount', 'Debit', 'Credit',
|
||||
'Applied', 'Sub-Total')
|
||||
);
|
||||
array_diff(array_keys($cols), array('Debit', 'Credit', 'Balance', 'Sub-Total', 'Comment')));
|
||||
|
||||
|
||||
@@ -3,22 +3,21 @@
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'id_sequence', 'formatter' => 'id');
|
||||
$cols['Name'] = array('index' => 'Ledger.name', 'formatter' => 'name');
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'longname');
|
||||
//$cols['Open Date'] = array('index' => 'PriorClose.stamp', 'formatter' => 'date');
|
||||
$cols['Close Date'] = array('index' => 'Close.stamp', 'formatter' => 'date');
|
||||
$cols['Open Date'] = array('index' => 'PriorCloseTransaction.stamp', 'formatter' => 'date');
|
||||
$cols['Close Date'] = array('index' => 'CloseTransaction.stamp', 'formatter' => 'date');
|
||||
$cols['Comment'] = array('index' => 'Ledger.comment', 'formatter' => 'comment');
|
||||
$cols['Entries'] = array('index' => 'entries', 'width' => '60', 'align' => 'right');
|
||||
$cols['Entries'] = array('index' => 'entries', 'formatter' => 'number');
|
||||
$cols['Debits'] = array('index' => 'debits', 'formatter' => 'currency');
|
||||
$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Account', 'Comment'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('ID', 'DESC')
|
||||
->defaultFields(array('ID', 'Account'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->sortField('ID', 'ASC')
|
||||
->defaultFields(array('ID', 'Name', 'Account'))
|
||||
->searchFields(array('Account', 'Comment'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Open Date', 'Comment')));
|
||||
|
||||
@@ -5,17 +5,15 @@ $cols = array();
|
||||
$cols['ID'] = array('index' => 'Map.id', 'formatter' => 'id');
|
||||
$cols['Name'] = array('index' => 'Map.name', 'formatter' => 'longname');
|
||||
$cols['Site Area'] = array('index' => 'SiteArea.name', 'formatter' => 'longname');
|
||||
$cols['Width'] = array('index' => 'Map.width', 'width' => '50', 'align' => 'right');
|
||||
$cols['Depth'] = array('index' => 'Map.depth', 'width' => '50', 'align' => 'right');
|
||||
$cols['Width'] = array('index' => 'Map.width', 'formatter' => 'number');
|
||||
$cols['Depth'] = array('index' => 'Map.depth', 'formatter' => 'number');
|
||||
$cols['Comment'] = array('index' => 'Map.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Name'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Name')
|
||||
->defaultFields(array('ID', 'Name'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Name'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array()));
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'MonetarySource.id', 'formatter' => 'id');
|
||||
$cols['Name'] = array('index' => 'MonetarySource.name', 'formatter' => 'longname');
|
||||
$cols['Comment'] = array('index' => 'MonetarySource.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('ID', 'Name'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('ID')
|
||||
->defaultFields(array('ID', 'Name'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
44
site/views/elements/statement_entries.ctp
Normal file
44
site/views/elements/statement_entries.ctp
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['Transaction'] = array('index' => 'Transaction.id', 'formatter' => 'id');
|
||||
$cols['Entry'] = array('index' => 'StatementEntry.id', 'formatter' => 'id');
|
||||
|
||||
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
|
||||
$cols['Effective'] = array('index' => 'StatementEntry.effective_date', 'formatter' => 'date');
|
||||
$cols['Through'] = array('index' => 'StatementEntry.through_date', 'formatter' => 'date');
|
||||
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name');
|
||||
|
||||
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Lease'] = array('index' => 'Lease.number', 'formatter' => 'id');
|
||||
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'shortname');
|
||||
|
||||
$cols['Comment'] = array('index' => 'StatementEntry.comment', 'formatter' => 'comment', 'width'=>150);
|
||||
|
||||
$cols['Charge'] = array('index' => 'charge', 'formatter' => 'currency');
|
||||
$cols['Payment'] = array('index' => 'payment', 'formatter' => 'currency');
|
||||
|
||||
$cols['Applied'] = array('index' => "applied", 'formatter' => 'currency');
|
||||
$cols['Sub-Total'] = array('index' => 'subtotal-balance', 'formatter' => 'currency', 'sortable' => false);
|
||||
|
||||
|
||||
if (isset($subtotal_column))
|
||||
$cols['Sub-Total']['index'] =
|
||||
'subtotal-' . $cols[$subtotal_column]['index'];
|
||||
|
||||
// Include custom data
|
||||
$grid->customData(compact('statement_entry_id'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Date')
|
||||
->defaultFields(array('Entry', 'Date', 'Charge', 'Payment'))
|
||||
->searchFields(array('Customer', 'Unit'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Through', 'Lease',
|
||||
'Applied', 'Sub-Total',
|
||||
'Comment')));
|
||||
|
||||
21
site/views/elements/tenders.ctp
Normal file
21
site/views/elements/tenders.ctp
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
//$cols['ID'] = array('index' => 'Tender.id', 'formatter' => 'id');
|
||||
$cols['Date'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
|
||||
$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Item'] = array('index' => 'Tender.name', 'formatter' => 'longname');
|
||||
$cols['Type'] = array('index' => 'TenderType.name', 'formatter' => 'name');
|
||||
$cols['Comment'] = array('index' => 'Tender.comment', 'formatter' => 'comment');
|
||||
$cols['Amount'] = array('index' => 'LedgerEntry.amount', 'formatter' => 'currency');
|
||||
$cols['Sub-Total'] = array('index' => 'subtotal-LedgerEntry.amount', 'formatter' => 'currency');
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Date')
|
||||
->defaultFields(array('Date', 'Name', 'Amount'))
|
||||
->searchFields(array('Name', 'Type'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Sub-Total')));
|
||||
@@ -3,18 +3,18 @@
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'Transaction.id', 'formatter' => 'id');
|
||||
$cols['Type'] = array('index' => 'Transaction.type', 'formatter' => 'enum');
|
||||
//$cols['Customer'] = array('index' => 'Customer.name', 'formatter' => 'longname');
|
||||
$cols['Timestamp'] = array('index' => 'Transaction.stamp', 'formatter' => 'date');
|
||||
$cols['Due'] = array('index' => 'Transaction.due_date', 'formatter' => 'date');
|
||||
$cols['Amount'] = array('index' => 'Transaction.amount', 'formatter' => 'currency');
|
||||
$cols['entries'] = array('index' => 'entries', 'formatter' => 'number');
|
||||
$cols['Comment'] = array('index' => 'Transaction.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Due', 'Comment'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('ID')
|
||||
->sortField('Timestamp')
|
||||
->defaultFields(array('ID', 'Timestamp'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Type', 'Comment'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Comment')));
|
||||
|
||||
@@ -3,22 +3,20 @@
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['Sort'] = array('index' => 'Unit.sort_order', 'hidden' => true);
|
||||
//$cols['Sort'] = array('index' => 'Unit.sort_order');
|
||||
//$cols['Walk'] = array('index' => 'Unit.walk_order');
|
||||
$cols['Walk'] = array('index' => 'Unit.walk_order', 'formatter' => 'number');
|
||||
$cols['ID'] = array('index' => 'Unit.id', 'formatter' => 'id');
|
||||
$cols['Unit'] = array('index' => 'Unit.name', 'width' => '50');
|
||||
$cols['Size'] = array('index' => 'UnitSize.name', 'width' => '75');
|
||||
$cols['Status'] = array('index' => 'Unit.status', 'width' => '75');
|
||||
$cols['Unit'] = array('index' => 'Unit.name', 'formatter' => 'shortname');
|
||||
$cols['Size'] = array('index' => 'UnitSize.name', 'formatter' => 'shortname');
|
||||
$cols['Rent'] = array('index' => 'Unit.rent', 'formatter' => 'currency');
|
||||
$cols['Status'] = array('index' => 'Unit.status', 'formatter' => 'name'); // We have enough real estate
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Comment'] = array('index' => 'Unit.comment', 'formatter' => 'comment');
|
||||
|
||||
// Set up search fields if requested by caller
|
||||
if (isset($searchfields))
|
||||
$grid->searchFields(array('Unit', 'Size', 'Status'));
|
||||
|
||||
// Render the grid
|
||||
$grid
|
||||
->columns($cols)
|
||||
->sortField('Sort')
|
||||
->defaultFields(array('Sort', 'ID', 'Unit'))
|
||||
->render($this, isset($config) ? $config : null);
|
||||
->searchFields(array('Unit', 'Size', 'Status'))
|
||||
->render($this, isset($config) ? $config : null,
|
||||
array_diff(array_keys($cols), array('Walk', 'Comment')));
|
||||
|
||||
@@ -41,13 +41,16 @@ class FormatHelper extends AppHelper {
|
||||
$date_fmt = 'm/d/Y';
|
||||
return (self::$time->format($date_fmt, $date) .
|
||||
($age
|
||||
? ' (' . self::age($date) . ')'
|
||||
? ' (' . self::age($date, 60*60*24) . ')'
|
||||
: ''));
|
||||
}
|
||||
|
||||
function datetime($datetime) {
|
||||
function datetime($datetime, $age = false) {
|
||||
if (!$datetime) return null;
|
||||
return self::$time->nice($datetime);
|
||||
return (self::$time->nice($datetime) .
|
||||
($age
|
||||
? ' (' . self::age($datetime) . ')'
|
||||
: ''));
|
||||
}
|
||||
|
||||
function phone($phone, $ext = null) {
|
||||
@@ -78,7 +81,7 @@ class FormatHelper extends AppHelper {
|
||||
return $comment;
|
||||
}
|
||||
|
||||
function age($datetime) {
|
||||
function age($datetime, $min_span = 0) {
|
||||
if (!isset($datetime))
|
||||
return null;
|
||||
|
||||
@@ -90,52 +93,81 @@ class FormatHelper extends AppHelper {
|
||||
$timeto = $backwards ? $seconds : $now;
|
||||
$span = $timeto - $timefrom;
|
||||
|
||||
// Display seconds if under 45 seconds
|
||||
if ($span === 0) {
|
||||
//pr(compact('now', 'seconds', 'backwards', 'timefrom', 'timeto', 'span', 'min_span'));
|
||||
|
||||
// If now, just return so
|
||||
if ($span === 0)
|
||||
return __('now', true);
|
||||
}
|
||||
if ($span < 45) {
|
||||
|
||||
// Display seconds if under 45 seconds
|
||||
if ($span < 45 && $span >= $min_span) {
|
||||
$approx = round($span);
|
||||
$unit = 'second';
|
||||
}
|
||||
|
||||
// Display minutes if under 45 minutes
|
||||
elseif (($span /= 60) < 45) {
|
||||
$approx = round($span);
|
||||
if (!isset($approx)) {
|
||||
$unit = 'minute';
|
||||
$span /= 60; $min_span /= 60;
|
||||
if ($span < 45 && ($span >= $min_span || $min_span <= 1))
|
||||
$approx = round($span);
|
||||
}
|
||||
// Display hours if under 18 hours
|
||||
elseif (($span /= 60) < 18) {
|
||||
$approx = round($span);
|
||||
$unit = 'hour';
|
||||
}
|
||||
// Display days if under 6.5 days
|
||||
elseif (($span /= 24) < 6.5) {
|
||||
$approx = round($span);
|
||||
$unit = 'day';
|
||||
}
|
||||
// Display weeks if less than 8 weeks
|
||||
elseif (($span /= 7) < 8) {
|
||||
$approx = round($span);
|
||||
$unit = 'week';
|
||||
}
|
||||
// Display months if less than 20 months
|
||||
elseif (($span /= (365.2425 / (7 * 12))) < 20) {
|
||||
$approx = round($span);
|
||||
$unit = 'month';
|
||||
|
||||
// Months are from 28-31 days. If it's too
|
||||
// close to being an exact month, just fudge
|
||||
// by saying the result is 'about' N months
|
||||
// instead of 'almost' or 'over' N months,
|
||||
// since we can't be accurate on this without
|
||||
// taking into account the day of the week.
|
||||
if ((abs($span - $approx) * (365.2425 / 12)) < 3)
|
||||
$relative = 'about';
|
||||
// Display hours if under 18 hours
|
||||
if (!isset($approx)) {
|
||||
$unit = 'hour';
|
||||
$span /= 60; $min_span /= 60;
|
||||
if ($span < 18 && ($span >= $min_span || $min_span <= 1))
|
||||
$approx = round($span);
|
||||
}
|
||||
else {
|
||||
$span /= 12;
|
||||
$approx = round($span);
|
||||
|
||||
// Display days if under 6.5 days
|
||||
if (!isset($approx)) {
|
||||
$unit = 'day';
|
||||
$span /= 24; $min_span /= 24;
|
||||
if ($span < 6.5 && ($span >= $min_span || $min_span <= 1))
|
||||
$approx = round($span);
|
||||
}
|
||||
|
||||
// Display weeks if less than 8 weeks
|
||||
if (!isset($approx)) {
|
||||
$unit = 'week';
|
||||
$span /= 7; $min_span /= 7;
|
||||
if ($span < 8 && ($span >= $min_span || $min_span <= 1))
|
||||
$approx = round($span);
|
||||
}
|
||||
|
||||
// Display months if less than 20 months
|
||||
if (!isset($approx)) {
|
||||
$unit = 'month';
|
||||
$span /= 365.2425 / (7*12); $min_span /= 365.2425 / (7*12);
|
||||
if ($span < 20 && ($span >= $min_span || $min_span <= 1)) {
|
||||
$approx = round($span);
|
||||
// Months are from 28-31 days. If it's too
|
||||
// close to being an exact month, just fudge
|
||||
// by saying the result is 'about' N months
|
||||
// instead of 'almost' or 'over' N months,
|
||||
// since we can't be accurate on this without
|
||||
// taking into account the day of the week.
|
||||
if ((abs($span - $approx) * (365.2425 / 12)) < 3)
|
||||
$relative = 'about';
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, just display years
|
||||
if (!isset($approx)) {
|
||||
$unit = 'year';
|
||||
$span /= 12; $min_span /= 12;
|
||||
$approx = round($span);
|
||||
}
|
||||
|
||||
//pr(compact('span', 'min_span', 'approx', 'unit'));
|
||||
|
||||
if ($approx == 0) {
|
||||
if ($unit == 'day')
|
||||
return __('today', true);
|
||||
|
||||
return __('this ' . $unit, true);
|
||||
}
|
||||
|
||||
return (__(isset($relative)
|
||||
|
||||
@@ -91,6 +91,7 @@ class GridHelper extends AppHelper {
|
||||
= array_map(create_function('$data',
|
||||
'return $data["id"];'),
|
||||
$items);
|
||||
$this->jqGrid_options['action'] = 'idlist';
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -159,6 +160,7 @@ class GridHelper extends AppHelper {
|
||||
$included = array_merge($included, $config['include']);
|
||||
if (isset($config['exclude']))
|
||||
$excluded = array_merge($excluded, $config['exclude']);
|
||||
unset($config['include'], $config['exclude']);
|
||||
|
||||
// Calculate the actual inclusion set
|
||||
$included = array_diff(array_merge($this->included, $included),
|
||||
@@ -168,6 +170,10 @@ class GridHelper extends AppHelper {
|
||||
$this->jqGrid_options['jqGridColumns']
|
||||
= array_intersect_key($this->columns, array_flip($included));
|
||||
|
||||
// Make sure search fields are all part of the inclusion set
|
||||
$this->jqGrid_options['search_fields']
|
||||
= array_intersect($this->jqGrid_options['search_fields'], $included);
|
||||
|
||||
// As an exception to the normal config variables,
|
||||
// handle 'rows' here. The reason is so that we
|
||||
// ease the burden on views which have a list of
|
||||
@@ -177,8 +183,17 @@ class GridHelper extends AppHelper {
|
||||
if (isset($config['rows'])) {
|
||||
// Shrink the limit... user can always override
|
||||
$this->id_list($config['rows'])->limit(10);
|
||||
unset($config['rows']);
|
||||
}
|
||||
|
||||
// One more exception, as the search fields get
|
||||
// defined, but not passed to jqGrid unless
|
||||
// specifically requested.
|
||||
if (isset($config['search']))
|
||||
unset($config['search']);
|
||||
else
|
||||
unset($this->jqGrid_options['search_fields']);
|
||||
|
||||
// Figure out what controller we're using to
|
||||
// populate the grid via ajax, and set it.
|
||||
$controller = $this->controller;
|
||||
@@ -196,7 +211,7 @@ class GridHelper extends AppHelper {
|
||||
|
||||
// Incorporate all other user options
|
||||
if (isset($config))
|
||||
$this->jqGrid_options = array_merge($this->jqGrid_options, $config);
|
||||
$this->jqGrid_options = array_merge_recursive($this->jqGrid_options, $config);
|
||||
|
||||
echo $view->element('jqGrid', $this->jqGrid_options);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
echo $html->meta('icon') . "\n";
|
||||
echo $html->css('cake.generic') . "\n";
|
||||
echo $html->css('layout') . "\n";
|
||||
echo $html->css('print', null, array('media' => 'print')) . "\n";
|
||||
echo $html->css('sidemenu') . "\n";
|
||||
//echo $html->css('jquery/base/ui.all') . "\n";
|
||||
//echo $html->css('jquery/smoothness/ui.all') . "\n";
|
||||
|
||||
@@ -11,17 +11,26 @@ echo ('<DIV CLASS="apply-deposit grid-selection-text">' .
|
||||
|
||||
'<DIV CLASS="supporting">' .
|
||||
'<TABLE>' .
|
||||
'<TR><TD CLASS="field">Balance:</TD><TD CLASS="value">'.$lease['stats']['balance'].'</TD></TR>' .
|
||||
'<TR><TD CLASS="field">Deposit:</TD><TD CLASS="value">'.$deposit['summary']['balance'].'</TD></TR>' .
|
||||
|
||||
/* '<TR><TD CLASS="field">Balance:</TD><TD CLASS="value">' . */
|
||||
/* FormatHelper::currency($lease['stats']['balance']) . */
|
||||
/* '</TD></TR>' . */
|
||||
|
||||
'<TR><TD CLASS="field">Deposit:</TD><TD CLASS="value">' .
|
||||
FormatHelper::currency($depositBalance) .
|
||||
'</TD></TR>' .
|
||||
|
||||
'</TABLE>' .
|
||||
'</DIV>' .
|
||||
|
||||
'</DIV>' . "\n");
|
||||
|
||||
|
||||
echo $form->create(null, array('id' => 'receipt-form',
|
||||
'url' => array('controller' => 'transactions',
|
||||
'action' => 'postReceipt')));
|
||||
echo $form->create(null, array('id' => 'apply-deposit-form',
|
||||
'url' => array('controller' => 'leases',
|
||||
'action' => 'apply_deposit')
|
||||
)
|
||||
);
|
||||
|
||||
echo $form->input("Customer.id",
|
||||
array('id' => 'customer-id',
|
||||
@@ -33,7 +42,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 +56,9 @@ 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',
|
||||
'opts' => array('value' => $depositBalance),
|
||||
),
|
||||
"comment" => array('opts' => array('size' => 50),
|
||||
),
|
||||
)));
|
||||
|
||||
@@ -154,7 +154,7 @@ function addChargeSource(flash) {
|
||||
($this->element('form_table',
|
||||
array('class' => "item invoice ledger-entry entry",
|
||||
//'with_name_after' => ':',
|
||||
'field_prefix' => 'LedgerEntry.%{id}',
|
||||
'field_prefix' => 'Entry.%{id}',
|
||||
'fields' => array
|
||||
("account_id" => array('name' => 'Account',
|
||||
'opts' =>
|
||||
@@ -164,11 +164,11 @@ function addChargeSource(flash) {
|
||||
),
|
||||
"effective_date" => array('opts' =>
|
||||
array('type' => 'text'),
|
||||
'between' => '<A HREF="#" ONCLICK="datepickerBOM(\'TransactionStamp\',\'LedgerEntry%{id}EffectiveDate\'); return false;">BOM</A>',
|
||||
'between' => '<A HREF="#" ONCLICK="datepickerBOM(\'TransactionStamp\',\'Entry%{id}EffectiveDate\'); return false;">BOM</A>',
|
||||
),
|
||||
"through_date" => array('opts' =>
|
||||
array('type' => 'text'),
|
||||
'between' => '<A HREF="#" ONCLICK="datepickerEOM(\'LedgerEntry%{id}EffectiveDate\',\'LedgerEntry%{id}ThroughDate\'); return false;">EOM</A>',
|
||||
'between' => '<A HREF="#" ONCLICK="datepickerEOM(\'Entry%{id}EffectiveDate\',\'Entry%{id}ThroughDate\'); return false;">EOM</A>',
|
||||
),
|
||||
"amount" => true,
|
||||
"comment" => array('opts' => array('size' => 50)),
|
||||
@@ -179,14 +179,14 @@ function addChargeSource(flash) {
|
||||
'</FIELDSET>'
|
||||
);
|
||||
|
||||
$("#LedgerEntry"+id+"EffectiveDate")
|
||||
$("#Entry"+id+"EffectiveDate")
|
||||
.attr('autocomplete', 'off')
|
||||
.datepicker({ constrainInput: true,
|
||||
numberOfMonths: [1, 1],
|
||||
showCurrentAtPos: 0,
|
||||
dateFormat: 'mm/dd/yy' });
|
||||
|
||||
$("#LedgerEntry"+id+"ThroughDate")
|
||||
$("#Entry"+id+"ThroughDate")
|
||||
.attr('autocomplete', 'off')
|
||||
.datepicker({ constrainInput: true,
|
||||
numberOfMonths: [1, 1],
|
||||
|
||||
@@ -16,29 +16,31 @@ $unit = $lease['Unit'];
|
||||
if (isset($lease['Lease']))
|
||||
$lease = $lease['Lease'];
|
||||
|
||||
$rows = array(array('ID', $lease['id']),
|
||||
array('Number', $lease['number']),
|
||||
array('Lease Type', $lease_type['name']),
|
||||
array('Unit', $html->link($unit['name'],
|
||||
$rows = array();
|
||||
|
||||
$rows[] = array('ID', $lease['id']);
|
||||
$rows[] = array('Number', $lease['number']);
|
||||
$rows[] = array('Lease Type', $lease_type['name']);
|
||||
$rows[] = array('Unit', $html->link($unit['name'],
|
||||
array('controller' => 'units',
|
||||
'action' => 'view',
|
||||
$unit['id']))),
|
||||
array('Customer', $html->link($customer['name'],
|
||||
$unit['id'])));
|
||||
$rows[] = array('Customer', $html->link($customer['name'],
|
||||
array('controller' => 'customers',
|
||||
'action' => 'view',
|
||||
$customer['id']))),
|
||||
array('Lease_Date', FormatHelper::date($lease['lease_date'], true)),
|
||||
array('Move-in Planned', FormatHelper::date($lease['movein_planned_date'], true)),
|
||||
array('Move-in', FormatHelper::date($lease['movein_date'], true)),
|
||||
array('Move-out', FormatHelper::date($lease['moveout_date'], true)),
|
||||
array('Move-out Planned', FormatHelper::date($lease['moveout_planned_date'], true)),
|
||||
array('Notice Given', FormatHelper::date($lease['notice_given_date'], true)),
|
||||
array('Notice Received', FormatHelper::date($lease['notice_received_date'], true)),
|
||||
array('Closed', FormatHelper::date($lease['close_date'], true)),
|
||||
array('Deposit', FormatHelper::currency($lease['deposit'])),
|
||||
array('Rent', FormatHelper::currency($lease['rent'])),
|
||||
array('Paid Through', FormatHelper::date($lease['paid_through'], true)),
|
||||
array('Comment', $lease['comment']));
|
||||
$customer['id'])));
|
||||
$rows[] = array('Lease_Date', FormatHelper::date($lease['lease_date'], true));
|
||||
$rows[] = array('Move-in Planned', FormatHelper::date($lease['movein_planned_date'], true));
|
||||
$rows[] = array('Move-in', FormatHelper::date($lease['movein_date'], true));
|
||||
$rows[] = array('Move-out', FormatHelper::date($lease['moveout_date'], true));
|
||||
$rows[] = array('Move-out Planned', FormatHelper::date($lease['moveout_planned_date'], true));
|
||||
$rows[] = array('Notice Given', FormatHelper::date($lease['notice_given_date'], true));
|
||||
$rows[] = array('Notice Received', FormatHelper::date($lease['notice_received_date'], true));
|
||||
$rows[] = array('Closed', FormatHelper::date($lease['close_date'], true));
|
||||
$rows[] = array('Deposit', FormatHelper::currency($lease['deposit']));
|
||||
$rows[] = array('Rent', FormatHelper::currency($lease['rent']));
|
||||
$rows[] = array('Paid Through', FormatHelper::date($lease['paid_through'], true));
|
||||
$rows[] = array('Comment', $lease['comment']);
|
||||
|
||||
|
||||
echo $this->element('table',
|
||||
@@ -79,16 +81,14 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
* Lease Account History
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'lease_id' => $lease['id'],
|
||||
'ar_account' => true,
|
||||
|
||||
// Grid configuration
|
||||
echo $this->element('statement_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Account',
|
||||
),
|
||||
));
|
||||
'filter' => array('Lease.id' => $lease['id']),
|
||||
'include' => array('Through'),
|
||||
'exclude' => array('Customer', 'Lease', 'Unit'),
|
||||
)));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
|
||||
@@ -2,57 +2,18 @@
|
||||
|
||||
echo '<div class="ledger-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"). This, when we provide
|
||||
// reconcile information, we're really providing reconcile info
|
||||
// for two independent accounts. The reconciling entries,
|
||||
// therefore, are those on the opposite side of the ledger in
|
||||
// each account. For example, assume this "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 ENTRY
|
||||
// | | | | |
|
||||
// | | | |75 75| <-- Deposit includes this entry
|
||||
// | | | | |
|
||||
//
|
||||
// In this case, we're looking to provide reconcile information
|
||||
// of A/R for (the credit side of) this entry, and also of Cash
|
||||
// (for the debit side). Taking the accounts as individual
|
||||
// entries, instead of the "double entry" representation in the
|
||||
// database, we're actually providing information on the two
|
||||
// A/R entries, 50 & 5, which are both debits, i.e. opposite
|
||||
// entries to the credit of A/R. The cash account entry
|
||||
// reconciles against the credit of 75. Again, this is the
|
||||
// opposite entry to the debit of Cash.
|
||||
//
|
||||
// Thus, for our debit_ledger_id, we're reconciling against
|
||||
// credits, and for our credit_ledger_id, against debits.
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* LedgerEntry Detail Main Section
|
||||
* Ledger Entry Detail Main Section
|
||||
*/
|
||||
|
||||
$transaction = $entry['Transaction'];
|
||||
$ledgers = array('debit' => $entry['DebitLedger'],
|
||||
'credit' => $entry['CreditLedger']);
|
||||
$source = $entry['MonetarySource'];
|
||||
$customer = $entry['Customer'];
|
||||
$lease = $entry['Lease'];
|
||||
$ledger = $entry['Ledger'];
|
||||
$account = $ledger['Account'];
|
||||
$tender = $entry['Tender'];
|
||||
$matching = $entry['MatchingEntry'];
|
||||
$entry = $entry['LedgerEntry'];
|
||||
|
||||
$rows = array();
|
||||
@@ -62,89 +23,43 @@ $rows[] = array('Transaction', $html->link('#'.$transaction['id'],
|
||||
'action' => 'view',
|
||||
$transaction['id'])));
|
||||
$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp']));
|
||||
$rows[] = array('Effective', FormatHelper::date($entry['effective_date']));
|
||||
$rows[] = array('Through', FormatHelper::date($entry['through_date']));
|
||||
$rows[] = array('Customer', (isset($customer['name'])
|
||||
? $html->link($customer['name'],
|
||||
array('controller' => 'customers',
|
||||
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
|
||||
$rows[] = array('Tender', $html->link($tender['name'],
|
||||
array('controller' => 'tenders',
|
||||
'action' => 'view',
|
||||
$tender['id'])));
|
||||
$rows[] = array('Account', $html->link($account['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$account['id'])));
|
||||
$rows[] = array('Ledger', $html->link($ledger['name'],
|
||||
array('controller' => 'ledgers',
|
||||
'action' => 'view',
|
||||
$ledger['id'])));
|
||||
$rows[] = array('Cr/Dr', ($entry['crdr'] .
|
||||
' (Matching ' . $matching['crdr'] . ': ' .
|
||||
$html->link('#'.$matching['id'],
|
||||
array('controller' => 'ledger_entries',
|
||||
'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('Monetary Source', (isset($source['name'])
|
||||
? $html->link($source['name'],
|
||||
array('controller' => 'monetary_sources',
|
||||
'action' => 'view',
|
||||
$source['id']))
|
||||
: null));
|
||||
$matching['id'])) .
|
||||
')'));
|
||||
$rows[] = array('Comment', $entry['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item ledger-entry detail',
|
||||
'caption' => 'Double Ledger Entry Detail',
|
||||
array('class' => 'item entry detail',
|
||||
'caption' => 'Ledger Entry Detail',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* LedgerEntry Info Box
|
||||
* Entry Info Box
|
||||
*/
|
||||
|
||||
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";
|
||||
|
||||
$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 CLASS="ledger-double-entry">' . "\n");
|
||||
foreach ($ledgers AS $type => $ledger) {
|
||||
$rows = array();
|
||||
|
||||
$rows[] = array('Account', $html->link($ledger['Account']['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$ledger['Account']['id'])));
|
||||
$rows[] = array('Ledger', $html->link('#' . $ledger['Account']['id']
|
||||
. '-' . $ledger['sequence'],
|
||||
array('controller' => 'ledgers',
|
||||
'action' => 'view',
|
||||
$ledger['id'])));
|
||||
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
|
||||
$rows[] = array('Effect', $ledger['Account']['ftype'] == $type ? 'INCREASE' : 'DECREASE');
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => array('item', $type, 'detail'),
|
||||
'caption' => ucfirst($type) . ' Ledger Entry',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
}
|
||||
echo ('</DIV>' . "\n");
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
@@ -156,30 +71,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";
|
||||
|
||||
|
||||
@@ -10,19 +10,21 @@ echo '<div class="ledger view">' . "\n";
|
||||
*/
|
||||
|
||||
$account = $ledger['Account'];
|
||||
//$close = $ledger['Close'];
|
||||
//$close = $ledger['CloseTransaction'];
|
||||
|
||||
if (isset($ledger['Ledger']))
|
||||
$ledger = $ledger['Ledger'];
|
||||
|
||||
$rows = array(array('ID', $ledger['id']),
|
||||
array('Account', $html->link($account['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$account['id']))),
|
||||
array('Sequence', $ledger['sequence']),
|
||||
array('Status', $ledger['close_id'] ? 'Closed' : 'Open'),
|
||||
array('Comment', $ledger['comment']));
|
||||
$rows = array();
|
||||
$rows[] = array('ID', $ledger['id']);
|
||||
$rows[] = array('Name', $ledger['name']);
|
||||
$rows[] = array('Account', $html->link($account['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$account['id'])));
|
||||
$rows[] = array('Sequence', $ledger['sequence']);
|
||||
$rows[] = array('Status', $ledger['close_transaction_id'] ? 'Closed' : 'Open');
|
||||
$rows[] = array('Comment', $ledger['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item ledger detail',
|
||||
@@ -64,16 +66,15 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'ledger_id' => $ledger['id'],
|
||||
'account_type' => $account['type'],
|
||||
'group_by_tx' => true,
|
||||
|
||||
// Grid configuration
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => "Ledger Entries",
|
||||
),
|
||||
));
|
||||
'filter' => array('Ledger.id' => $ledger['id']),
|
||||
'exclude' => array('Ledger', 'Account',
|
||||
'Amount', 'Cr/Dr', 'Balance',
|
||||
empty($account['payments']) ? 'Tender' : null),
|
||||
'include' => array('Debit', 'Credit', 'Sub-Total'),
|
||||
)));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<div class="monetary-source view">' . "\n";
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* MonetarySource Detail Main Section
|
||||
*/
|
||||
|
||||
$source = $monetarySource['MonetarySource'];
|
||||
|
||||
$rows = array(array('ID', $source['id']),
|
||||
array('Name', $source['name']),
|
||||
array('Data 1', $source['data1']),
|
||||
array('Data 2', $source['data2']),
|
||||
array('Data 3', $source['data3']),
|
||||
array('Data 4', $source['data4']),
|
||||
array('Comment', $source['comment']));
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item monetary-source detail',
|
||||
'caption' => 'Monetary Source Detail',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* MonetarySource Info Box
|
||||
*/
|
||||
|
||||
echo '<div class="infobox">' . "\n";
|
||||
$rows = array();
|
||||
echo $this->element('table',
|
||||
array('class' => 'summary',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value'),
|
||||
'suppress_alternate_rows' => true,
|
||||
));
|
||||
echo '</div>' . "\n";
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* Supporting Elements Section
|
||||
*/
|
||||
|
||||
echo '<div CLASS="detail supporting">' . "\n";
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Ledger Entries
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'monetary_source_id' => $source['id'],
|
||||
|
||||
// Grid configuration
|
||||
'config' => array
|
||||
('caption' => "Ledger Entries",
|
||||
),
|
||||
));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
/* End page div */
|
||||
echo '</div>' . "\n";
|
||||
123
site/views/statement_entries/view.ctp
Normal file
123
site/views/statement_entries/view.ctp
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<div class="statement-entry view">' . "\n";
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* Entry Detail Main Section
|
||||
*/
|
||||
|
||||
$transaction = $entry['Transaction'];
|
||||
$account = $entry['LedgerEntry'][0]['Account'];
|
||||
$customer = $entry['Customer'];
|
||||
$lease = $entry['Lease'];
|
||||
$entry = $entry['StatementEntry'];
|
||||
|
||||
$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($entry['effective_date']));
|
||||
$rows[] = array('Through', FormatHelper::date($entry['through_date']));
|
||||
$rows[] = array('Type', $entry['type']);
|
||||
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
|
||||
$rows[] = array('Account', $html->link($account['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$account['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 statement-entry detail',
|
||||
'caption' => 'Statement Entry Detail',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Entry Info Box
|
||||
*/
|
||||
|
||||
if (strtoupper($entry['type']) === 'CHARGE') {
|
||||
$applied_caption = "Payments Applied";
|
||||
//$remaining_caption = "Charge Balance";
|
||||
}
|
||||
else {
|
||||
$applied_caption = "Applied to Charges";
|
||||
//$remaining_caption = "Payment Balance";
|
||||
}
|
||||
|
||||
$remaining_caption = "Remaining Balance";
|
||||
|
||||
echo '<div class="infobox">' . "\n";
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array($applied_caption,
|
||||
'<SPAN id="statement-entry-applied">' .
|
||||
FormatHelper::currency($stats['reconciled']) .
|
||||
'</SPAN>');
|
||||
$rows[] = array($remaining_caption,
|
||||
'<SPAN id="statement-entry-balance">' .
|
||||
FormatHelper::currency($stats['balance']) .
|
||||
'</SPAN>');
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item summary',
|
||||
'caption' => null,
|
||||
'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
|
||||
*/
|
||||
|
||||
echo $this->element('statement_entries', array
|
||||
(// Element configuration
|
||||
'statement_entry_id' => $entry['id'],
|
||||
/* 'action' => 'reconcile', */
|
||||
|
||||
// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Entries Applied',
|
||||
//'filter' => array('id' => $entry['id']),
|
||||
'exclude' => array('Entry'),
|
||||
)));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
/* End page div */
|
||||
echo '</div>' . "\n";
|
||||
166
site/views/tenders/deposit.ctp
Normal file
166
site/views/tenders/deposit.ctp
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<div class="tender 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('depositTypes', 'depositAccounts'));
|
||||
|
||||
echo $form->create(null, array('id' => 'deposit-form',
|
||||
'onsubmit' => 'return verifyRequest();',
|
||||
'url' => array('controller' => 'transactions',
|
||||
'action' => 'postDeposit')));
|
||||
|
||||
foreach ($depositTypes AS $type) {
|
||||
$names = Inflector::pluralize($type['name']);
|
||||
|
||||
$radioOptions =
|
||||
array('none' => " No {$names} will be deposited",
|
||||
'all' => (" Deposit all {$names} (" .
|
||||
FormatHelper::currency($type['stats']['undeposited']) .
|
||||
")"),
|
||||
'subset' => " Deposit {$names} from the list below",
|
||||
);
|
||||
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.selection",
|
||||
array('type' => 'radio',
|
||||
'class' => "type-selection-{$type['id']}",
|
||||
'separator' => '<BR>',
|
||||
'onclick' => "switchSelection({$type['id']})",
|
||||
'legend' => false,
|
||||
'value' => $type['stats']['undeposited'] > 0 ? 'all' : 'none',
|
||||
'disabled' => $type['stats']['undeposited'] <= 0,
|
||||
'options' => $radioOptions,
|
||||
));
|
||||
|
||||
// REVISIT <AP>: 20090729
|
||||
// Would like to present an option for the user to close the ledger
|
||||
// associated with the form of tender, or to just leave it open.
|
||||
// For now, just close it.
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.close",
|
||||
array('type' => 'hidden',
|
||||
'value' => true,
|
||||
));
|
||||
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.amount",
|
||||
array('type' => 'hidden',
|
||||
'value' => $type['stats']['undeposited'],
|
||||
));
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.id",
|
||||
array('type' => 'hidden',
|
||||
'value' => $type['id'],
|
||||
));
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.name",
|
||||
array('type' => 'hidden',
|
||||
'value' => $type['name'],
|
||||
));
|
||||
echo "\n";
|
||||
|
||||
$grid_div_id = "tenders-{$type['id']}-list";
|
||||
echo $this->element('tenders', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'grid_div_id' => $grid_div_id,
|
||||
|
||||
'grid_setup' =>
|
||||
array('hiddengrid' => true,
|
||||
'multiselect' => true),
|
||||
|
||||
'caption' => "{$names} on hand",
|
||||
'filter' => array('deposit_transaction_id' => null,
|
||||
'TenderType.id' => $type['id']),
|
||||
'exclude' => array('Type'),
|
||||
),
|
||||
));
|
||||
|
||||
// Add a hidden item to hold the jqGrid selection,
|
||||
// which we'll populate prior to form submission.
|
||||
echo "\n";
|
||||
echo $form->input("TenderType.{$type['id']}.items",
|
||||
array('type' => 'hidden',
|
||||
'value' => null
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
echo $form->input('Deposit.Account.id', array('label' => 'Deposit Account ',
|
||||
'options' => $depositAccounts));
|
||||
echo $form->end('Perform Deposit');
|
||||
|
||||
/* End page div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
?>
|
||||
|
||||
<script type="text/javascript"><!--
|
||||
$(document).ready(function(){
|
||||
<?php foreach ($depositTypes AS $type): ?>
|
||||
<?php /* Hide the multiselect column */ ?>
|
||||
switchSelection(<?php echo $type['id']; ?>);
|
||||
<?php endforeach; ?>
|
||||
});
|
||||
|
||||
// pre-submit callback
|
||||
function verifyRequest() {
|
||||
<?php foreach ($depositTypes AS $type): ?>
|
||||
var rows = $('#<?php echo "tenders-{$type['id']}-list-jqGrid"; ?>').getGridParam('selarrrow');
|
||||
$('#<?php echo "TenderType{$type['id']}Items"; ?>').val(serialize(rows));
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php
|
||||
// REVISIT <AP>: 20090730
|
||||
// Verify the request before submitting
|
||||
?>
|
||||
|
||||
// return false to prevent the form from being submitted;
|
||||
// anything other than false will allow submission.
|
||||
return true;
|
||||
}
|
||||
|
||||
function switchSelection(type_id) {
|
||||
var grid_div_id = '#tenders-'+type_id+'-list';
|
||||
var grid_id = grid_div_id+'-jqGrid';
|
||||
var selection = $('.type-selection-'+type_id+':checked').val();
|
||||
var gridstate = $(grid_id).getGridParam('gridstate');
|
||||
|
||||
<?php
|
||||
// It seems that jqGrid doesn't work too well with multiselect
|
||||
// dynamically enabled / disabled. What we'd like to do is:
|
||||
|
||||
/* if (selection == 'subset' && !multiselect) */
|
||||
/* $(grid_id).setGridParam({multiselect:true}).showCol('cb'); */
|
||||
/* if (selection != 'subset' && multiselect) */
|
||||
/* $(grid_id).setGridParam({multiselect:false}).hideCol('cb'); */
|
||||
|
||||
// However, if the grid is reloaded (manually, or page switch) while
|
||||
// multiselect is disabled, then it loads garbage. I have been able
|
||||
// to work around this using the loadBeforeSend event to re-enable
|
||||
// multiselect just for the load, then disable it again using this
|
||||
// function after the load (using the gridComplete event).
|
||||
//
|
||||
// It seems terribly clunky though, and so as a workaround, I've
|
||||
// found that I can leave multiselect enabled all the time, and just
|
||||
// Tell the grid to allow selection through checkboxes only, after
|
||||
// hiding the checkboxes. This essentially disables multiselection
|
||||
// as well, without the grid having to disable the entire mechanism.
|
||||
?>
|
||||
|
||||
// Configure multiselection
|
||||
if (selection == 'subset')
|
||||
$(grid_id).showCol('cb').setGridParam({multiboxonly: false});
|
||||
else
|
||||
$(grid_id).hideCol('cb').setGridParam({multiboxonly: true}).resetSelection();
|
||||
|
||||
// Show or hide the grid, as appropriate
|
||||
if ((selection == 'subset') == (gridstate == 'hidden'))
|
||||
$(grid_div_id + ' .HeaderButton').click();
|
||||
}
|
||||
|
||||
--></script>
|
||||
100
site/views/tenders/view.ctp
Normal file
100
site/views/tenders/view.ctp
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
echo '<div class="tender view">' . "\n";
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* Tender Detail Main Section
|
||||
*/
|
||||
|
||||
$ttype = $tender['TenderType'];
|
||||
$customer = $tender['Customer'];
|
||||
$entry = $tender['LedgerEntry'];
|
||||
$transaction = $entry['Transaction'];
|
||||
$tender = $tender['Tender'];
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array('ID', $tender['id']);
|
||||
$rows[] = array('Received', FormatHelper::date($transaction['stamp']));
|
||||
$rows[] = array('Customer', $html->link($customer['name'],
|
||||
array('controller' => 'customers',
|
||||
'action' => 'view',
|
||||
$customer['id'])));
|
||||
$rows[] = array('Amount', FormatHelper::currency($entry['amount']));
|
||||
$rows[] = array('Item', $tender['name']);
|
||||
$rows[] = array('Type', $ttype['name']);
|
||||
/* $rows[] = array('Type', $html->link($ttype['name'], */
|
||||
/* array('controller' => 'tender_types', */
|
||||
/* 'action' => 'view', */
|
||||
/* $ttype['id']))); */
|
||||
|
||||
for ($i=1; $i<=4; ++$i)
|
||||
if (!empty($ttype["data{$i}_name"]))
|
||||
$rows[] = array($ttype["data{$i}_name"], $tender["data{$i}"]);
|
||||
|
||||
if (!empty($tender['deposit_transaction_id']))
|
||||
$rows[] = array('Deposit', $html->link('#'.$tender['deposit_transaction_id'],
|
||||
array('controller' => 'transactions',
|
||||
'action' => 'view',
|
||||
$tender['deposit_transaction_id'])));
|
||||
|
||||
if (!empty($tender['nsf_transaction_id']))
|
||||
$rows[] = array('NSF', $html->link('#'.$tender['nsf_transaction_id'],
|
||||
array('controller' => 'transactions',
|
||||
'action' => 'view',
|
||||
$tender['nsf_transaction_id'])));
|
||||
|
||||
$rows[] = array('Comment', $tender['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item tender detail',
|
||||
'caption' => 'Legal Tender Detail',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value')));
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Tender Info Box
|
||||
*/
|
||||
|
||||
echo '<div class="infobox">' . "\n";
|
||||
$rows = array();
|
||||
echo $this->element('table',
|
||||
array('class' => 'summary',
|
||||
'rows' => $rows,
|
||||
'column_class' => array('field', 'value'),
|
||||
'suppress_alternate_rows' => true,
|
||||
));
|
||||
echo '</div>' . "\n";
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
**********************************************************************
|
||||
* Supporting Elements Section
|
||||
*/
|
||||
|
||||
echo '<div CLASS="detail supporting">' . "\n";
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Ledger Entries
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => "Ledger Entries",
|
||||
'filter' => array('id' => array($tender['ledger_entry_id'], $tender['nsf_ledger_entry_id'])),
|
||||
'exclude' => array('Tender'),
|
||||
)));
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
echo '</div>' . "\n";
|
||||
|
||||
/* End page div */
|
||||
echo '</div>' . "\n";
|
||||
67
site/views/transactions/deposit_slip.ctp
Normal file
67
site/views/transactions/deposit_slip.ctp
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php /* -*- mode:PHP -*- */
|
||||
|
||||
//style="display:inline;
|
||||
echo('<H2 style="display:inline;">Deposit Slip: ' .
|
||||
FormatHelper::datetime($deposit['Transaction']['stamp'])
|
||||
. '</H2>' . "\n");
|
||||
/* echo('(' . */
|
||||
/* FormatHelper::age($deposit['Transaction']['stamp'], 60) */
|
||||
/* . ')<BR>' . "\n"); */
|
||||
|
||||
//pr(compact('deposit'));
|
||||
|
||||
// Handle account summaries
|
||||
$rows = array();
|
||||
$row_class = array();
|
||||
foreach ($deposit['types'] AS $type) {
|
||||
$row_class[] = array();
|
||||
$rows[] = array($type['name'].':',
|
||||
FormatHelper::_n($type['count'], 'Item'),
|
||||
FormatHelper::currency($type['total'], true));
|
||||
}
|
||||
$row_class[] = 'grand';
|
||||
$rows[] = array('Deposit Total:',
|
||||
null,
|
||||
FormatHelper::currency($deposit['Transaction']['amount'], true));
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'deposit-summary',
|
||||
'rows' => $rows,
|
||||
'row_class' => $row_class,
|
||||
'column_class' => array('account', 'quantity', 'total'),
|
||||
'suppress_alternate_rows' => true,
|
||||
));
|
||||
|
||||
|
||||
// Print out the items of each ledger
|
||||
if (0) {
|
||||
foreach ($deposit['types'] AS $type) {
|
||||
echo $this->element('tenders', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'grid_div_id' => "tenders-{$type['id']}-list",
|
||||
'caption' => $type['name'] . ' Items',
|
||||
'filter' => array('deposit_transaction_id'
|
||||
=> $deposit['Transaction']['id'],
|
||||
'TenderType.id'
|
||||
=> $type['id'],
|
||||
),
|
||||
'exclude' => array('Type'),
|
||||
)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo $this->element('tenders', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'caption' => 'Deposited Items',
|
||||
'filter' => array('deposit_transaction_id'
|
||||
=> $deposit['Transaction']['id'],
|
||||
),
|
||||
)));
|
||||
}
|
||||
|
||||
/* End page div */
|
||||
//echo '</div>' . "\n";
|
||||
@@ -9,10 +9,26 @@ echo '<div class="transaction view">' . "\n";
|
||||
* Transaction Detail Main Section
|
||||
*/
|
||||
|
||||
$rows = array(array('ID', $transaction['Transaction']['id']),
|
||||
array('Timestamp', FormatHelper::datetime($transaction['Transaction']['stamp'])),
|
||||
array('Due', FormatHelper::date($transaction['Transaction']['due_date'])),
|
||||
array('Comment', $transaction['Transaction']['comment']));
|
||||
$account = $transaction['Account'];
|
||||
$ledger = $transaction['Ledger'];
|
||||
|
||||
if (isset($transaction['Transaction']))
|
||||
$transaction = $transaction['Transaction'];
|
||||
|
||||
$rows = array();
|
||||
$rows[] = array('ID', $transaction['id']);
|
||||
$rows[] = array('Type', $transaction['type']);
|
||||
$rows[] = array('Timestamp', FormatHelper::datetime($transaction['stamp']));
|
||||
$rows[] = array('Amount', FormatHelper::currency($transaction['amount']));
|
||||
$rows[] = array('Account', $html->link($account['name'],
|
||||
array('controller' => 'accounts',
|
||||
'action' => 'view',
|
||||
$account['id'])));
|
||||
$rows[] = array('Ledger', $html->link($ledger['name'],
|
||||
array('controller' => 'ledgers',
|
||||
'action' => 'view',
|
||||
$ledger['id'])));
|
||||
$rows[] = array('Comment', $transaction['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item transaction detail',
|
||||
@@ -27,7 +43,7 @@ echo $this->element('table',
|
||||
|
||||
echo '<div class="infobox">' . "\n";
|
||||
$rows = array();
|
||||
$rows[] = array('Total:', FormatHelper::currency($total));
|
||||
$rows[] = array('Total:', FormatHelper::currency($transaction['amount']));
|
||||
echo $this->element('table',
|
||||
array('class' => 'summary',
|
||||
'rows' => $rows,
|
||||
@@ -48,24 +64,51 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Entries
|
||||
* Statement Entries
|
||||
*/
|
||||
|
||||
if ($transaction['type'] === 'INVOICE' || $transaction['type'] === 'RECEIPT') {
|
||||
echo $this->element('statement_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'caption' => 'Statement Entries',
|
||||
'filter' => array('Transaction.id' => $transaction['id'],
|
||||
'type !=' => 'VOID'),
|
||||
'exclude' => array('Transaction', 'Account'),
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
* Ledger Entries
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'transaction_id' => $transaction['Transaction']['id'],
|
||||
|
||||
// Default for grouping by transaction is already false,
|
||||
// but we'll get explicit here, since we clearly want to
|
||||
// see all of the ledger entries not grouped by tx.
|
||||
'group_by_tx' => false,
|
||||
|
||||
// Grid configuration
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'caption' => 'Entries in Transaction',
|
||||
),
|
||||
));
|
||||
'caption' => 'Ledger Entries',
|
||||
'filter' => array('Transaction.id' => $transaction['id'],
|
||||
'Account.id !=' => $account['id']),
|
||||
'exclude' => array('Transaction'),
|
||||
)));
|
||||
|
||||
|
||||
/* /\********************************************************************** */
|
||||
/* * Tenders Deposited */
|
||||
/* *\/ */
|
||||
|
||||
/* if ($transaction['type'] === 'DEPOSIT') { */
|
||||
/* echo $this->element('tenders', array */
|
||||
/* (// Grid configuration */
|
||||
/* 'config' => array */
|
||||
/* ( */
|
||||
/* 'caption' => 'Deposited Items', */
|
||||
/* 'filter' => array('deposit_transaction_id' => $transaction['id']), */
|
||||
/* ))); */
|
||||
/* } */
|
||||
|
||||
|
||||
|
||||
/* End "detail supporting" div */
|
||||
|
||||
@@ -16,12 +16,13 @@ $unit_size = $unit['UnitSize'];
|
||||
if (isset($unit['Unit']))
|
||||
$unit = $unit['Unit'];
|
||||
|
||||
$rows = array(array('Name', $unit['name']),
|
||||
array('Status', $unit['status']),
|
||||
array('Size', $unit_size['name']),
|
||||
array('Deposit', FormatHelper::currency($unit['deposit'])),
|
||||
array('Rent', FormatHelper::currency($unit['rent'])),
|
||||
array('Comment', $unit['comment']));
|
||||
$rows = array();
|
||||
$rows[] = array('Name', $unit['name']);
|
||||
$rows[] = array('Status', $unit['status']);
|
||||
$rows[] = array('Size', $unit_size['name']);
|
||||
$rows[] = array('Deposit', FormatHelper::currency($unit['deposit']));
|
||||
$rows[] = array('Rent', FormatHelper::currency($unit['rent']));
|
||||
$rows[] = array('Comment', $unit['comment']);
|
||||
|
||||
echo $this->element('table',
|
||||
array('class' => 'item unit detail',
|
||||
@@ -62,9 +63,11 @@ echo '<div CLASS="detail supporting">' . "\n";
|
||||
*/
|
||||
|
||||
echo $this->element('leases', array
|
||||
('config' => array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
('caption' => 'Lease History',
|
||||
'rows' => $leases,
|
||||
'filter' => array('Unit.id' => $unit['id']),
|
||||
'exclude' => array('Unit'),
|
||||
)));
|
||||
|
||||
|
||||
@@ -73,20 +76,18 @@ echo $this->element('leases', array
|
||||
*/
|
||||
|
||||
if (isset($current_lease['id'])) {
|
||||
echo $this->element('ledger_entries', array
|
||||
(// Element configuration
|
||||
'ar_account' => true,
|
||||
'lease_id' => $current_lease['id'],
|
||||
|
||||
// Grid configuration
|
||||
echo $this->element('statement_entries', array
|
||||
(// Grid configuration
|
||||
'config' => array
|
||||
(
|
||||
'caption' =>
|
||||
('Current Lease Account ('
|
||||
. $current_lease['Customer']['name']
|
||||
. ')'),
|
||||
),
|
||||
));
|
||||
'filter' => array('Lease.id' => $current_lease['id']),
|
||||
'include' => array('Through'),
|
||||
'exclude' => array('Customer', 'Lease', 'Unit'),
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -209,6 +209,12 @@ div.related {
|
||||
}
|
||||
|
||||
/* Debugging */
|
||||
.pr-caller {
|
||||
color: #000;
|
||||
background: #c8c;
|
||||
/* padding-top: 0.2em; */
|
||||
padding: 0.1em;
|
||||
}
|
||||
pre {
|
||||
color: #000;
|
||||
background: #f0f0f0;
|
||||
|
||||
@@ -126,7 +126,6 @@ div.detail.supporting { clear : both;
|
||||
*/
|
||||
|
||||
div.infobox { float: right;
|
||||
width: 39%;
|
||||
margin-top: 2.0em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
@@ -194,6 +193,22 @@ table.list.ledger td.date.receipt { padding-left: 1em; }
|
||||
table.list.ledger td.evnrow { background: #f4f4f4; }
|
||||
|
||||
|
||||
/************************************************************
|
||||
************************************************************
|
||||
* Receipt Entry
|
||||
*/
|
||||
|
||||
input.payment {
|
||||
width: 10em;
|
||||
}
|
||||
label.payment {
|
||||
padding-left: 0.5em;
|
||||
/* float: left; */
|
||||
/* text-align: right; */
|
||||
/* display: block; */
|
||||
}
|
||||
|
||||
|
||||
/************************************************************
|
||||
************************************************************
|
||||
* Special cases
|
||||
@@ -224,6 +239,10 @@ table.deposit-summary td.quantity { padding-right: 0.8em; }
|
||||
form#collected-form input[type=button] { float : left;
|
||||
clear : left; }
|
||||
|
||||
/* NSF items */
|
||||
.nsf-tender { text-decoration: line-through; }
|
||||
|
||||
|
||||
/************************************************************
|
||||
************************************************************
|
||||
* jqGrid
|
||||
@@ -243,6 +262,19 @@ div.loading {
|
||||
margin-left: 1.0em;
|
||||
}
|
||||
|
||||
div.scroll .selbox {
|
||||
font-family: 'lucida grande',verdana,helvetica,arial,sans-serif;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
/* The "page N / M" input box */
|
||||
div.scroll input[type='text']
|
||||
{
|
||||
text-align: right;
|
||||
padding-right: 0.4em;
|
||||
}
|
||||
|
||||
|
||||
/************************************************************
|
||||
************************************************************
|
||||
* Grid Dynamic Selection Text
|
||||
@@ -314,12 +346,20 @@ fieldset fieldset div {
|
||||
clear: left;
|
||||
/* margin: 0 20px; */
|
||||
}
|
||||
form div {
|
||||
clear: both;
|
||||
/* margin-bottom: 1em; */
|
||||
/* padding: .5em; */
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
/*
|
||||
* REVISIT <AP>: 20090728
|
||||
* This "form div" is way too generic, and in fact
|
||||
* it's screwing up the jqGrid header. I'm commenting
|
||||
* it out for now, to see if it actually is needed
|
||||
* anywhere, and hope to delete it in the near future.
|
||||
*/
|
||||
/* form div { */
|
||||
/* clear: both; */
|
||||
/* /\* margin-bottom: 1em; *\/ */
|
||||
/* /\* padding: .5em; *\/ */
|
||||
/* vertical-align: text-top; */
|
||||
/* } */
|
||||
form div.input {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
46
site/webroot/css/print.css
Normal file
46
site/webroot/css/print.css
Normal file
@@ -0,0 +1,46 @@
|
||||
/************************************************************
|
||||
************************************************************
|
||||
* Styles for media type: print
|
||||
*/
|
||||
|
||||
/* No need for the menu */
|
||||
table#layout td#sidecolumn
|
||||
{ display: none; }
|
||||
|
||||
/* No need for the debug kit */
|
||||
div#debug-kit-toolbar
|
||||
{ display: none; }
|
||||
|
||||
/* In fact, no need for any debug stuff */
|
||||
.debug
|
||||
{ display: none; }
|
||||
|
||||
|
||||
/************************************************************
|
||||
* Grid display
|
||||
*/
|
||||
|
||||
/* Grid are not to be truncated when printing, so
|
||||
* set overflow to visible for necessary selectors.
|
||||
*/
|
||||
div#content, div.grid_bdiv
|
||||
{ overflow: visible ! important }
|
||||
|
||||
div.grid_hdiv
|
||||
{ border-bottom: 3px double #000; }
|
||||
|
||||
/* The header is generally useless, except for the <th>,
|
||||
* as well as the footer navtable. The pagination buttons
|
||||
* are of no use, nor is the selbox to set # of rows.
|
||||
*/
|
||||
.GridHeader td,
|
||||
div.scroll .navtable,
|
||||
div.scroll .pgbuttons,
|
||||
div.scroll select.selbox
|
||||
{ display: none; }
|
||||
|
||||
/* The "page N / M" input box... make it look like normal text */
|
||||
div.scroll input[type='text'] {
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
45
todo.notes
45
todo.notes
@@ -15,27 +15,13 @@ the customer/lease infobox used to report only PAID
|
||||
security deposits, but it now seems like it's reporting ALL
|
||||
security deposits charged.
|
||||
|
||||
Having a grid of ledger entries grouped by transaction appears
|
||||
to work, from the financial aspect, but the count of entries
|
||||
is incorrect. The problem is the grouping only occurs after
|
||||
the count, which it has to in order for the count to work. We
|
||||
need to obliterate the group_by_tx parameter, and simply use
|
||||
the transanction controller to generate the grid instead of
|
||||
ledger_entries.
|
||||
|
||||
Handle a credit, ensuring that it's applied to new charges
|
||||
- either automatically;
|
||||
- by user opt-in to use credits when invoicing
|
||||
- by user opt-in when entering a receipt
|
||||
- by manually allowing a receipt of credits
|
||||
Customer Selection on the Receipt Page is broken.
|
||||
(Selecting a row and waiting for the update).
|
||||
|
||||
Get Petty Cash working. We'll need to add one or more expense
|
||||
accounts. We'll also need to implement purchase order
|
||||
functionality, or at least simple an expense page.
|
||||
|
||||
Reconcile all entries of a ledger to the c/f entry when
|
||||
"closing" the ledger and creating a new one.
|
||||
|
||||
Automatic assessment of rents, or at least for now, one
|
||||
click manual mechanism to assess rents correctly for all
|
||||
tenants.
|
||||
@@ -54,10 +40,6 @@ should add a needs-locked status.
|
||||
|
||||
Same as above, except needs-to-be-unlocked.
|
||||
|
||||
Determine when each unit is paid up until. There is actually
|
||||
two things here: invoiced up until, and paid up until. One or
|
||||
both of these should be displayed on the Lease view page.
|
||||
|
||||
Make the default (initial) jqGrid sort order for balance be DESC.
|
||||
|
||||
MUST reports:
|
||||
@@ -127,3 +109,26 @@ for $16.33.
|
||||
Figure out how to utilize the security deposit, whether
|
||||
as part of move-out only, or as one of the payment options.
|
||||
|
||||
Having a grid of ledger entries grouped by transaction appears
|
||||
to work, from the financial aspect, but the count of entries
|
||||
is incorrect. The problem is the grouping only occurs after
|
||||
the count, which it has to in order for the count to work. We
|
||||
need to obliterate the group_by_tx parameter, and simply use
|
||||
the transanction controller to generate the grid instead of
|
||||
ledger_entries.
|
||||
|
||||
Handle a credit, ensuring that it's applied to new charges
|
||||
- either automatically;
|
||||
- by user opt-in to use credits when invoicing
|
||||
- by user opt-in when entering a receipt
|
||||
- by manually allowing a receipt of credits
|
||||
|
||||
Reconcile all entries of a ledger to the c/f entry when
|
||||
"closing" the ledger and creating a new one.
|
||||
|
||||
Determine when each unit is paid up until. There is actually
|
||||
two things here: invoiced up until, and paid up until. One or
|
||||
both of these should be displayed on the Lease view page.
|
||||
|
||||
20090729: New Ledger doesn't seem to give a balance forward entry.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user