Finally, finally... a working version for payment entry. The current schema is working well, and seems to handle our technical needs. However, it does seem to be very confusing with the extra accounts. Nonetheless, it does work and so I'll keep going down this path. This checkin also includes a mechanism to close the books on an account (by closing the underlying ledger) and start a new ledger. One of the decisions worth revisiting is separating out ledger entries that are really part of the same transaction. Without this change, inspecting a transaction results in the transaction total being off by a factor of two, since all money movement is in their twice (once for the expected reason, and again to hit the invoice/receipt ledger).
git-svn-id: file:///svn-source/pmgr/branches/invoice_receipt_20090629@195 97e9348a-65ac-dc4b-aefc-98561f571b83
This commit is contained in:
@@ -1007,8 +1007,20 @@ CREATE TABLE `pmgr_monetary_sources` (
|
||||
-- REVISIT <AP>: 20090605
|
||||
-- Check Number;
|
||||
-- Routing Number, Account Number;
|
||||
-- Card Number, Expiration Date; CCV2 Code
|
||||
-- Card Number, Expiration Date; CVV2 Code
|
||||
-- etc.
|
||||
-- REVISIT <AP> 20090630
|
||||
-- I _think_ that CVV2 is NEVER supposed to
|
||||
-- be stored ANYWHERE. Merchants agree to
|
||||
-- use it only to verify the transaction and
|
||||
-- then leave no record of it, so that even
|
||||
-- if their security is compromised, no one
|
||||
-- will know the CVV2 code unless they are
|
||||
-- in physical possession of the card.
|
||||
`data1` VARCHAR(80) DEFAULT NULL,
|
||||
`data2` VARCHAR(80) DEFAULT NULL,
|
||||
`data3` VARCHAR(80) DEFAULT NULL,
|
||||
`data4` VARCHAR(80) DEFAULT NULL,
|
||||
|
||||
`comment` VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
|
||||
@@ -868,6 +868,13 @@ foreach $row (@{query($sdbh, $query)}) {
|
||||
'amount' => $row->{'InvoiceAmount'},
|
||||
} };
|
||||
|
||||
addRow('transactions',
|
||||
{ 'stamp' => datefmt($row->{'ChargeDate'}),
|
||||
'through_date' => datefmt($row->{'EndDate'}) });
|
||||
|
||||
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'}
|
||||
= $newdb{'tables'}{'transactions'}{'autoid'};
|
||||
|
||||
# Invoice must debit the A/R ledger...
|
||||
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'debit_ledger_id'}
|
||||
= $newdb{'lookup'}{'account'}{'A/R'}{'ledger_id'};
|
||||
@@ -900,7 +907,7 @@ $query = "SELECT * FROM Charges ORDER BY ChargeID";
|
||||
foreach $row (@{query($sdbh, $query)}) {
|
||||
|
||||
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'tx'}
|
||||
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'tx'};
|
||||
= $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'invoice'}{'charge_tx'};
|
||||
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'}
|
||||
= $newdb{'lookup'}{'ledger'}{$row->{'LedgerID'}}{'customer_id'};
|
||||
$newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'lease_id'}
|
||||
@@ -1077,7 +1084,15 @@ foreach $row (@{query($sdbh, $query)}) {
|
||||
{ 'stamp' => datefmt($row->{'ReceiptDate'}),
|
||||
'through_date' => undef });
|
||||
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}
|
||||
= {'tx' => $newdb{'tables'}{'transactions'}{'autoid'} };
|
||||
= {'tx' => $newdb{'tables'}{'transactions'}{'autoid'},
|
||||
'date' => datefmt($row->{'ReceiptDate'}),
|
||||
};
|
||||
|
||||
addRow('transactions',
|
||||
{ 'stamp' => datefmt($row->{'ReceiptDate'}),
|
||||
'through_date' => undef });
|
||||
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'payment_tx'}
|
||||
= $newdb{'tables'}{'transactions'}{'autoid'};
|
||||
}
|
||||
|
||||
if ($row->{'ReceiptDate'} =~ m%3/25/2009%) {
|
||||
@@ -1098,12 +1113,15 @@ foreach $row (@{query($sdbh, $query)}) {
|
||||
# Set up a monetary source for the receipt,
|
||||
if (!$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}) {
|
||||
my $name = $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'name'};
|
||||
my $data1;
|
||||
if ($name eq 'Check') {
|
||||
$name = 'Check #' . $row->{'CheckNum'};
|
||||
$data1 = $row->{'CheckNum'};
|
||||
}
|
||||
addRow('monetary_sources',
|
||||
{ 'monetary_type_id' => $newdb{'lookup'}{'payment_type'}{$row->{'PaymentType'}}{'monetary_type'}{'id'},
|
||||
'name' => $name,
|
||||
'data1' => $data1,
|
||||
'comment' => "Receipt:$row->{'ReceiptNum'}; Payment:$row->{'PaymentType'}; Check:$row->{'CheckNum'}" });
|
||||
|
||||
$newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{$row->{'PaymentType'}}{'monetary_source_id'}
|
||||
@@ -1148,7 +1166,15 @@ $newdb{'lookup'}{'payment'} = {};
|
||||
$query = "SELECT * FROM Payments ORDER BY PaymentID";
|
||||
foreach $row (@{query($sdbh, $query)})
|
||||
{
|
||||
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}} = {};
|
||||
# addRow('transactions',
|
||||
# { 'stamp' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'date'},
|
||||
# 'through_date' => undef });
|
||||
# $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}
|
||||
# = {'tx' => $newdb{'tables'}{'transactions'}{'autoid'} };
|
||||
|
||||
$newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}
|
||||
= { 'tx' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'payment_tx'} };
|
||||
|
||||
|
||||
# NOTE THE ABOVE LARGE COMMENT BLOCK
|
||||
# The choice of credit/debit ledgers does not mirror the
|
||||
@@ -1172,7 +1198,7 @@ foreach $row (@{query($sdbh, $query)})
|
||||
# debit: Receipt credit: A/R
|
||||
addRow('ledger_entries',
|
||||
{ 'monetary_source_id' => $newdb{'ids'}{'monetary_source'}{'internal'},
|
||||
'transaction_id' => $newdb{'lookup'}{'receipt'}{$row->{'ReceiptNum'}}{'tx'},
|
||||
'transaction_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'tx'},
|
||||
'debit_ledger_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'debit_ledger_id'},
|
||||
'credit_ledger_id' => $newdb{'lookup'}{'payment'}{$row->{'PaymentID'}}{'credit_ledger_id'},
|
||||
'customer_id' => $newdb{'lookup'}{'charge'}{$row->{'ChargeID'}}{'customer_id'},
|
||||
|
||||
@@ -137,7 +137,16 @@ class AppModel extends Model {
|
||||
*/
|
||||
|
||||
function dateFormatBeforeSave($dateString) {
|
||||
return date('Y-m-d', strtotime($dateString));
|
||||
/* $time = ''; */
|
||||
/* if (preg_match('/(\d+(:\d+))/', $dateString, $match)) */
|
||||
/* $time = ' '.$match[1]; */
|
||||
/* $dateString = preg_replace('/(\d+(:\d+))/', '', $dateString); */
|
||||
/* return date('Y-m-d', strtotime($dateString)) . $time; */
|
||||
|
||||
if (preg_match('/:/', $dateString))
|
||||
return date('Y-m-d H:i:s', strtotime($dateString));
|
||||
else
|
||||
return date('Y-m-d', strtotime($dateString));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -119,6 +119,25 @@ class AccountsController extends AppController {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* action: newledger
|
||||
* - Close the current account ledger and create a new one,
|
||||
* carrying forward any balance if necessary.
|
||||
*/
|
||||
|
||||
function newledger($id = null) {
|
||||
if (!$this->Account->closeCurrentLedger($id)) {
|
||||
$this->Session->setFlash(__('Unable to create new Ledger.', true));
|
||||
}
|
||||
if ($id)
|
||||
$this->redirect(array('action'=>'view', $id));
|
||||
else
|
||||
$this->redirect(array('action'=>'index'));
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -160,8 +179,14 @@ class AccountsController extends AppController {
|
||||
$stats = $this->Account->stats($id, true);
|
||||
$stats = $stats['Ledger'];
|
||||
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'Operations', 'header' => true);
|
||||
$this->sidemenu_links[] =
|
||||
array('name' => 'New Ledger', 'url' => array('action' => 'newledger', $id));
|
||||
|
||||
// Prepare to render
|
||||
$title = 'Account: ' . $account['Account']['name'];
|
||||
$this->set(compact('account', 'title', 'stats'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -103,186 +103,204 @@ class TransactionsController extends AppController {
|
||||
*/
|
||||
|
||||
function postReceipt() {
|
||||
$this->autoRender = false;
|
||||
|
||||
if (!$this->RequestHandler->isPost()) {
|
||||
echo('<H2>THIS IS NOT A POST FOR SOME REASON</H2>');
|
||||
return;
|
||||
}
|
||||
//pr(array('thisdata' => $this->data));
|
||||
|
||||
if (isset($this->data['customer_id'])) {
|
||||
$C = new Customer();
|
||||
$C->recursive = -1;
|
||||
$customer = $C->find
|
||||
('first',
|
||||
array('contain' => array('Account.CurrentLedger.id'),
|
||||
'fields' => false,
|
||||
'conditions' => array('Customer.id', $this->data['customer_id']),
|
||||
));
|
||||
$ledger_id = $customer['Account']['CurrentLedger']['id'];
|
||||
}
|
||||
else {
|
||||
// Payment by Unit, Lease, etc
|
||||
$ledger_id = 0;
|
||||
$this->layout = null;
|
||||
$this->autoLayout = false;
|
||||
$this->autoRender = false;
|
||||
Configure::write('debug', '0');
|
||||
|
||||
// Sanitize the transaction data
|
||||
if (!$this->data['Transaction']['comment'])
|
||||
$this->data['Transaction']['comment'] = null;
|
||||
|
||||
if(empty($this->data['Transaction']['stamp'])) {
|
||||
die("Time/Date not valid");
|
||||
}
|
||||
pr($this->data['Transaction']);
|
||||
|
||||
$amount = 0;
|
||||
foreach ($this->data['LedgerEntry'] AS &$entry) {
|
||||
$reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'],
|
||||
'credit',
|
||||
$entry['amount']);
|
||||
pr(compact('entry', 'reconciled'));
|
||||
|
||||
foreach ($reconciled['debit']['entry'] AS $rec) {
|
||||
$entry['DebitReconciliationLedgerEntry'] =
|
||||
array('amount' => $rec['applied'],
|
||||
//'debit_ledger_entry_id'
|
||||
'credit_ledger_entry_id' => $rec['id']
|
||||
);
|
||||
}
|
||||
|
||||
$amount += isset($entry['amount']) ? $entry['amount'] : 0;
|
||||
$entry['debit_ledger_id'] = 6; // Cash/Payments
|
||||
$entry['credit_ledger_id'] = $ledger_id;
|
||||
}
|
||||
|
||||
|
||||
pr($this->data);
|
||||
$T = new Transaction();
|
||||
$T->create();
|
||||
if ($T->saveAll($this->data,
|
||||
array(
|
||||
'validate' => false,
|
||||
//'fieldList' => array(),
|
||||
//'callbacks' => true,
|
||||
))) {
|
||||
$tid = $T->id;
|
||||
$this->Session->setFlash(__("New Transaction Created ($tid)!", true));
|
||||
//$this->redirect(array('action'=>'view', $mid));
|
||||
}
|
||||
else {
|
||||
$this->autoRender = false;
|
||||
pr(array('checkpoint' => "saveAll failed"));
|
||||
}
|
||||
pr($T->data);
|
||||
|
||||
// Create some models for convenience
|
||||
$A = new Account();
|
||||
$C = new Customer();
|
||||
$LE = new LedgerEntry();
|
||||
/* $reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'], */
|
||||
/* 'credit', */
|
||||
/* $amount); */
|
||||
/* pr(compact('amount', 'unreconciled', 'reconciled')); */
|
||||
/* return; */
|
||||
|
||||
// Create a transaction for the receipt
|
||||
$receipt_transaction = new Transaction();
|
||||
$receipt_transaction->create();
|
||||
if (!$receipt_transaction->save($this->data['Transaction'],
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "receipt transaction save failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Transaction Created ({$receipt_transaction->id})!");
|
||||
$receipt_transaction->read();
|
||||
pr($receipt_transaction->data);
|
||||
|
||||
// Create a transaction for the splits
|
||||
$split_transaction = new Transaction();
|
||||
$split_transaction->create();
|
||||
if (!$split_transaction->save($this->data['Transaction'],
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "split transaction save failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Transaction Created ({$split_transaction->id})!");
|
||||
$split_transaction->read();
|
||||
pr($split_transaction->data);
|
||||
|
||||
// Go through the entered payments
|
||||
foreach ($this->data['LedgerEntry'] AS &$entry) {
|
||||
|
||||
// Get the Monetary Source squared away
|
||||
if ($entry['monetary_type_name'] === 'Cash') {
|
||||
// No distinguishing features of Cash, just
|
||||
// use the shared monetary source
|
||||
$entry['monetary_source_id'] =
|
||||
$this->Transaction->LedgerEntry->MonetarySource->nameToID('Cash');
|
||||
unset($entry['MonetarySource']);
|
||||
}
|
||||
else {
|
||||
// The monetary source needs to be unique
|
||||
// Create a new one dedicated to this entry
|
||||
$entry['MonetarySource']['monetary_type_id'] =
|
||||
$this->Transaction->LedgerEntry->MonetarySource->MonetaryType
|
||||
->nameToID($entry['monetary_type_name']);
|
||||
|
||||
$entry['MonetarySource']['name'] =
|
||||
$this->Transaction->LedgerEntry->MonetarySource->MonetaryType
|
||||
->nameToID($entry['monetary_type_name']);
|
||||
|
||||
// Give it a fancy name based on the check number
|
||||
$entry['MonetarySource']['name'] = $entry['monetary_type_name'];
|
||||
if ($entry['monetary_type_name'] === 'Check' ||
|
||||
$entry['monetary_type_name'] === 'Money Order') {
|
||||
$entry['MonetarySource']['name'] .=
|
||||
' #' . $entry['MonetarySource']['data1'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This entry of physical money is part of the receipt transaction
|
||||
// debit: Cash/Check/Etc credit: Receipt
|
||||
$entry['transaction_id'] = $receipt_transaction->id;
|
||||
|
||||
// Receipt must debit the "money" asset (bank, cash, check, etc)...
|
||||
$entry['debit_ledger_id']
|
||||
= $A->currentLedgerID($A->nameToID($entry['monetary_type_name']));
|
||||
|
||||
// ...and credit the Receipt ledger
|
||||
$entry['credit_ledger_id']
|
||||
= $A->currentLedgerID($A->receiptAccountID());
|
||||
|
||||
$entry['customer_id']
|
||||
= $this->data['customer_id'];
|
||||
|
||||
// Create it
|
||||
$receipt_entry = new LedgerEntry();
|
||||
$receipt_entry->create();
|
||||
if (!$receipt_entry->saveAll($entry,
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "receipt entry saveAll failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Receipt LedgerEntry Created ({$receipt_entry->id})!");
|
||||
$receipt_entry->read();
|
||||
pr($receipt_entry->data);
|
||||
|
||||
$reconciled = $C->reconcileNewLedgerEntry($this->data['customer_id'],
|
||||
'credit',
|
||||
$entry['amount']);
|
||||
pr(compact('entry', 'reconciled'));
|
||||
continue;
|
||||
|
||||
foreach ($reconciled['debit']['entry'] AS $rec) {
|
||||
$data = array('LedgerEntry' =>
|
||||
array('DebitReconciliationLedgerEntry' =>
|
||||
array('amount' => $rec['applied'],
|
||||
//'debit_ledger_entry_id'
|
||||
'credit_ledger_entry_id' => $rec['id']
|
||||
),
|
||||
),
|
||||
);
|
||||
//'DebitReconciliationLedgerEntry' => array(
|
||||
//pr(compact('amount', 'unreconciled', 'reconciled'));
|
||||
foreach (array_merge($reconciled['debit']['entry'], array
|
||||
(array('id' => null,
|
||||
'applied' => $reconciled['debit']['unapplied'],
|
||||
'customer_id' => $this->data['customer_id'],
|
||||
'lease_id' => null))) AS $rec) {
|
||||
|
||||
pr(array('checkpoint' => "Handle Reconciled Entry",
|
||||
compact('rec'),
|
||||
));
|
||||
if (!$rec['applied'])
|
||||
continue;
|
||||
|
||||
// Create an entry to handle the splitting of the funds ("Payment")
|
||||
// Payment must debit the Receipt ledger, and credit the A/R ledger
|
||||
// debit: Receipt credit: A/R
|
||||
$split_entry_data = array
|
||||
('debit_ledger_id' => $A->currentLedgerID($A->receiptAccountID()),
|
||||
'credit_ledger_id' => $A->currentLedgerID($A->accountReceivableAccountID()),
|
||||
'transaction_id' => $split_transaction->id,
|
||||
'amount' => $rec['applied'],
|
||||
'lease_id' => $rec['lease_id'],
|
||||
'customer_id' => $rec['customer_id'],
|
||||
);
|
||||
|
||||
// Create a new split entry from the data
|
||||
$split_entry = new LedgerEntry();
|
||||
$split_entry->create();
|
||||
if (!$split_entry->save($split_entry_data,
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "split entry save failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Split LedgerEntry Created ({$split_entry->id})!");
|
||||
$split_entry->read();
|
||||
pr($split_entry->data);
|
||||
|
||||
// Reconcile the Receipt account. Our two entries look like:
|
||||
// debit: Cash/Check/Etc credit: Receipt
|
||||
// debit: Receipt credit: A/R
|
||||
// Since this is from the perspective of the Receipt account,
|
||||
// the debit entry is the Receipt<->A/R, and the credit
|
||||
// entry is the actual receipt ledger entry.
|
||||
$R = new Reconciliation();
|
||||
$R->create();
|
||||
if (!$R->save(array('debit_ledger_entry_id' => $split_entry->id,
|
||||
'credit_ledger_entry_id' => $receipt_entry->id,
|
||||
'amount' => $rec['applied']),
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "receipt reconcile save failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Receipt Reconciliation Created ({$R->id})!");
|
||||
$R->read();
|
||||
pr($R->data);
|
||||
|
||||
// Only reconcile the A/R account if we have an entry
|
||||
// to reconcile with, otherwise, just go on.
|
||||
if (!$rec['id'])
|
||||
continue;
|
||||
|
||||
// Reconcile the A/R account. Our two entries look like:
|
||||
// debit: Receipt credit: A/R
|
||||
// debit: A/R credit: Invoice
|
||||
// Since this is from the perspective of the A/R account,
|
||||
// the debit entry is the Invoice<->A/R, and the credit
|
||||
// entry is the Receipt<->A/R.
|
||||
$R = new Reconciliation();
|
||||
$R->create();
|
||||
if (!$R->save(array('debit_ledger_entry_id' => $rec['id'],
|
||||
'credit_ledger_entry_id' => $split_entry->id,
|
||||
'amount' => $rec['applied']),
|
||||
array('validate' => false,
|
||||
))) {
|
||||
pr(array('checkpoint' => "split reconcile save failed"));
|
||||
die("Unknown Database Failure");
|
||||
}
|
||||
pr("New Split Reconciliation Created ({$R->id})!");
|
||||
$R->read();
|
||||
pr($R->data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function saveTest() {
|
||||
$data =
|
||||
array(
|
||||
/* 'Customer' => array */
|
||||
/* ('id' => 7, */
|
||||
/* ), */
|
||||
|
||||
'LedgerEntry' => array
|
||||
(
|
||||
'0' => array(
|
||||
'amount' => 100,
|
||||
'debit_ledger_id' => 1,
|
||||
'credit_ledger_id' => 2,
|
||||
'MonetarySource' => array('name' => 'testmoney', 'monetary_type_id' => 2),
|
||||
),
|
||||
'1' => array(
|
||||
'amount' => 101,
|
||||
'debit_ledger_id' => 1,
|
||||
'credit_ledger_id' => 2,
|
||||
'MonetarySource' => array('name' => 'testmoney2', 'monetary_type_id' => 2),
|
||||
),
|
||||
),
|
||||
|
||||
'Transaction' => array
|
||||
(
|
||||
'stamp' => '06/18/2009',
|
||||
'comment' => 'no comment',
|
||||
),
|
||||
);
|
||||
|
||||
$data =
|
||||
/* array( */
|
||||
/* 'LedgerEntry' => array */
|
||||
/* ( */
|
||||
/* '0' => */
|
||||
array(
|
||||
'amount' => 100,
|
||||
'debit_ledger_id' => 1,
|
||||
'credit_ledger_id' => 2,
|
||||
'transaction_id' => 66,
|
||||
'DebitReconciliationLedgerEntry' =>
|
||||
array('amount' => 44,
|
||||
//'debit_ledger_entry_id'
|
||||
'credit_ledger_entry_id' => 17,
|
||||
),
|
||||
/* ), */
|
||||
/* ), */
|
||||
|
||||
);
|
||||
|
||||
//$M = new Transaction();
|
||||
$M = new LedgerEntry();
|
||||
$M->create();
|
||||
$retval = $M->saveAll($data,
|
||||
array(
|
||||
'validate' => false,
|
||||
'fieldList' => array(),
|
||||
'callbacks' => true,
|
||||
));
|
||||
|
||||
$mid = $M->id;
|
||||
pr(compact('retval', 'mid'));
|
||||
if ($mid) {
|
||||
$this->Session->setFlash(__("New Transaction Created ($mid)!", true));
|
||||
//$this->redirect(array('action'=>'view', $mid));
|
||||
}
|
||||
else {
|
||||
$this->autoRender = false;
|
||||
pr(array('checkpoint' => "saveAll failed"));
|
||||
}
|
||||
|
||||
|
||||
/* $LE = new LedgerEntry(); */
|
||||
/* $LE->create(); */
|
||||
/* $ret = $LE->save($data, */
|
||||
/* array( */
|
||||
/* 'validate' => false, */
|
||||
/* 'fieldList' => array(), */
|
||||
/* 'callbacks' => true, */
|
||||
/* )); */
|
||||
/* $leid = $LE->id; */
|
||||
/* pr(array('checkpoint' => "New Ledger Entry", */
|
||||
/* compact('leid', 'ret'))); */
|
||||
//pr($LE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,6 +92,23 @@ class Account extends AppModel {
|
||||
function invoiceAccountID() { return $this->nameToID('Invoice'); }
|
||||
function receiptAccountID() { return $this->nameToID('Receipt'); }
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: currentLedgerID
|
||||
* - Returns the current ledger ID of the account
|
||||
*/
|
||||
function currentLedgerID($id) {
|
||||
$this->cacheQueries = true;
|
||||
$item = $this->find('first', array
|
||||
('contain' => 'CurrentLedger',
|
||||
'conditions' => array('Account.id' => $id),
|
||||
));
|
||||
$this->cacheQueries = false;
|
||||
return $item['CurrentLedger']['id'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -131,6 +148,34 @@ class Account extends AppModel {
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: closeCurrentLedger
|
||||
* - Closes the current account ledger, and opens a new one
|
||||
* with the old balance carried forward.
|
||||
*/
|
||||
function closeCurrentLedger($id = null) {
|
||||
$contain = array('CurrentLedger' => array('fields' => array('CurrentLedger.id')));
|
||||
|
||||
$this->cacheQueries = true;
|
||||
$account = $this->find('all', array
|
||||
('contain' => $contain,
|
||||
'fields' => array(),
|
||||
'conditions' =>
|
||||
$id ? array(array('Account.id' => $id)) : array()
|
||||
));
|
||||
$this->cacheQueries = false;
|
||||
//pr(compact('id', 'account'));
|
||||
|
||||
foreach ($account AS $acct) {
|
||||
if (!$this->Ledger->closeLedger($acct['CurrentLedger']['id']))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
@@ -222,7 +267,7 @@ class Account extends AppModel {
|
||||
('fields' => array(),
|
||||
"LedgerEntry" => array
|
||||
('class' => "{$ucfund}LedgerEntry",
|
||||
'fields' => array('id', 'amount'),
|
||||
'fields' => array('id', 'customer_id', 'lease_id', 'amount'),
|
||||
"ReconciliationLedgerEntry" => array
|
||||
('class' => "{$ucfund}ReconciliationLedgerEntry",
|
||||
'fields' => array
|
||||
|
||||
@@ -44,6 +44,72 @@ class Ledger extends AppModel {
|
||||
);
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
* function: closeLedger
|
||||
* - Closes the current ledger, and returns a fresh one
|
||||
*/
|
||||
function closeLedger($id) {
|
||||
$this->recursive = -1;
|
||||
|
||||
$stamp = date('Y-m-d G:i:s');
|
||||
$this->id = $id;
|
||||
$this->read();
|
||||
$this->data['Ledger']['close_stamp'] = $stamp;
|
||||
$this->data['Ledger']['closed'] = 1;
|
||||
$this->save($this->data, false);
|
||||
|
||||
$stats = $this->stats($id);
|
||||
|
||||
$this->read();
|
||||
$this->data['Ledger']['id'] = null;
|
||||
$this->data['Ledger']['closed'] = 0;
|
||||
$this->data['Ledger']['open_stamp'] = $stamp;
|
||||
$this->data['Ledger']['close_stamp'] = null;
|
||||
$this->data['Ledger']['comment'] = null;
|
||||
++$this->data['Ledger']['sequence'];
|
||||
$this->id = null;
|
||||
$this->save($this->data, false);
|
||||
//pr($this->data);
|
||||
|
||||
if ($stats['balance'] == 0)
|
||||
return $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(),
|
||||
array('validate' => false,
|
||||
))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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",
|
||||
);
|
||||
|
||||
$carry_entry = new LedgerEntry();
|
||||
$carry_entry->create();
|
||||
if (!$carry_entry->save($carry_entry_data,
|
||||
array('validate' => false,
|
||||
))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
**************************************************************************
|
||||
**************************************************************************
|
||||
|
||||
@@ -8,6 +8,16 @@ class LedgerEntry extends AppModel {
|
||||
'amount' => array('money')
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
'DebitReconciliation' => array(
|
||||
'className' => 'Reconciliation',
|
||||
'foreignKey' => 'debit_ledger_entry_id',
|
||||
),
|
||||
'CreditReconciliation' => array(
|
||||
'className' => 'Reconciliation',
|
||||
'foreignKey' => 'credit_ledger_entry_id',
|
||||
),
|
||||
);
|
||||
var $belongsTo = array(
|
||||
'MonetarySource',
|
||||
'Transaction',
|
||||
|
||||
15
site/models/reconciliation.php
Normal file
15
site/models/reconciliation.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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',
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ class Transaction extends AppModel {
|
||||
/* ); */
|
||||
|
||||
var $belongsTo = array(
|
||||
'Customer',
|
||||
);
|
||||
|
||||
var $hasMany = array(
|
||||
@@ -17,10 +16,15 @@ class Transaction extends AppModel {
|
||||
|
||||
function beforeSave() {
|
||||
|
||||
if(!empty($this->data['Transaction']['stamp'])) {
|
||||
if(isset($this->data['Transaction']['stamp']) &&
|
||||
$this->data['Transaction']['stamp'] !== 'CURRENT_TIMESTAMP') {
|
||||
$this->data['Transaction']['stamp'] =
|
||||
$this->dateFormatBeforeSave($this->data['Transaction']['stamp']);
|
||||
}
|
||||
else {
|
||||
$this->data['Transaction']['stamp'] = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ echo $this->element('ledgers',
|
||||
*/
|
||||
|
||||
echo $this->element('ledger_entries',
|
||||
array('caption' => "Current Ledger: (#{$account['CurrentLedger']['sequence']})",
|
||||
array('caption' => "Current Ledger: (#{$account['Account']['id']}-{$account['CurrentLedger']['sequence']})",
|
||||
'ledger_id' => $account['CurrentLedger']['id'],
|
||||
'account_type' => $account['Account']['type'],
|
||||
));
|
||||
|
||||
@@ -148,6 +148,17 @@ $grid_setup['onSelectRow'] = array
|
||||
$('#payments').html('');
|
||||
$('#payment-id').val(0);
|
||||
addPaymentSource(false);
|
||||
datepickerNow();
|
||||
}
|
||||
|
||||
function datepickerNow() {
|
||||
now = new Date();
|
||||
$("#datepicker").val($.datepicker.formatDate('mm/dd/yy', now)
|
||||
+ ' '
|
||||
+ (now.getHours() < 10 ? '0' : '')
|
||||
+ now.getHours() + ':'
|
||||
+ (now.getMinutes() < 10 ? '0' : '')
|
||||
+ now.getMinutes());
|
||||
}
|
||||
|
||||
function addPaymentSource(flash) {
|
||||
@@ -158,16 +169,6 @@ $grid_setup['onSelectRow'] = array
|
||||
|
||||
'<DIV ID="payment-type-div-%{id}">' +
|
||||
<?php
|
||||
/* REVISIT <AP> 20090616:
|
||||
* MUST GET THIS FROM THE DATABASE!!
|
||||
* HARDCODED VALUES BAD... VERY BAD...
|
||||
*/
|
||||
$monetary_type_ids = array('Cash' => 2,
|
||||
'Check' => 3,
|
||||
'Money Order' => 4,
|
||||
'ACH' => 5,
|
||||
'Credit Card' => 7,
|
||||
);
|
||||
|
||||
$types = array();
|
||||
foreach(array('Cash', 'Check', 'Money Order', /*'ACH', 'Credit Card'*/) AS $name)
|
||||
@@ -175,11 +176,10 @@ $grid_setup['onSelectRow'] = array
|
||||
|
||||
foreach ($types AS $type => $name) {
|
||||
$div = '<DIV>';
|
||||
$div .= '<INPUT TYPE="hidden" NAME="data[LedgerEntry][%{id}][bogus]" VALUE="1">';
|
||||
$div .= '<INPUT TYPE="radio" NAME="data[LedgerEntry][%{id}][MonetarySource][monetary_type_id]"';
|
||||
$div .= '<INPUT TYPE="radio" NAME="data[LedgerEntry][%{id}][monetary_type_name]"';
|
||||
$div .= ' ONCLICK="switchPaymentType(%{id}, \\\''.$type.'\\\')"';
|
||||
$div .= ' CLASS="payment-type-%{id}" ID="payment-type-'.$type.'-%{id}"';
|
||||
$div .= ' VALUE="'.$monetary_type_ids[$name].'" ' . ($name == 'Cash' ? 'CHECKED ' : '') . '/>';
|
||||
$div .= ' VALUE="'.$name.'" ' . ($name == 'Cash' ? 'CHECKED ' : '') . '/>';
|
||||
$div .= ' <LABEL FOR="payment-type-'.$type.'-%{id}">'.$name.'</LABEL>';
|
||||
$div .= '</DIV>';
|
||||
echo "'$div' +\n";
|
||||
@@ -228,7 +228,7 @@ function switchPaymentType(paymentid, type) {
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-check-number-'+paymentid+'">Check Number</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]"' +
|
||||
' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data1]"' +
|
||||
' ID="payment-check-number-'+paymentid+'" />' +
|
||||
'</DIV>';
|
||||
break;
|
||||
@@ -238,7 +238,7 @@ function switchPaymentType(paymentid, type) {
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-moneyorder-number-'+paymentid+'">Money Order Number</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]"' +
|
||||
' <INPUT TYPE="text" SIZE="6" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data1]"' +
|
||||
' ID="payment-moneyorder-number-'+paymentid+'" />' +
|
||||
'</DIV>';
|
||||
break;
|
||||
@@ -248,14 +248,14 @@ function switchPaymentType(paymentid, type) {
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-ach-routing-'+paymentid+'">Routing Number</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="9" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]"' +
|
||||
' <INPUT TYPE="text" SIZE="9" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data1]"' +
|
||||
' ID="payment-ach-routing-'+paymentid+'" />' +
|
||||
'</DIV>' +
|
||||
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-ach-account-'+paymentid+'">Account Number</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="17" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]"' +
|
||||
' <INPUT TYPE="text" SIZE="17" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data2]"' +
|
||||
' ID="payment-ach-account-'+paymentid+'" />' +
|
||||
'</DIV>';
|
||||
break;
|
||||
@@ -265,21 +265,21 @@ function switchPaymentType(paymentid, type) {
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-creditcard-account-'+paymentid+'">Account Number</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="16" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]' +
|
||||
' <INPUT TYPE="text" SIZE="16" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data1]' +
|
||||
' ID="payment-creditcard-account-'+paymentid+'" />' +
|
||||
'</DIV>' +
|
||||
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-creditcard-expiration-'+paymentid+'">Expiration Date</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="10" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][comment]' +
|
||||
' <INPUT TYPE="text" SIZE="10" NAME="data[LedgerEntry]['+paymentid+'][MonetarySource][data2]' +
|
||||
' ID="payment-creditcard-expiration-'+paymentid+'" />' +
|
||||
' </DIV>' +
|
||||
|
||||
'<DIV CLASS="input text required">' +
|
||||
' <LABEL FOR="payment-creditcard-cvv2-'+paymentid+'">CVV2 Code</LABEL>' +
|
||||
// REVISIT <AP>: 20090617: Use comment field for now.
|
||||
' <INPUT TYPE="text" SIZE="10" NAME=data[LedgerEntry]['+paymentid+'][MonetarySource][comment]' +
|
||||
' <INPUT TYPE="text" SIZE="10" NAME=data[LedgerEntry]['+paymentid+'][MonetarySource][data3]' +
|
||||
' ID="payment-creditcard-cvv2-'+paymentid+'" />' +
|
||||
'</DIV>';
|
||||
break;
|
||||
@@ -436,7 +436,8 @@ echo $form->create(null, array('id' => 'payment-form',
|
||||
|
||||
<?php
|
||||
|
||||
echo 'Date: <input id="datepicker" name="data[Transaction][stamp]" type="text" /><BR>' . "\n";
|
||||
echo 'Date: <input id="datepicker" name="data[Transaction][stamp]" type="text" />';
|
||||
echo ' <A HREF="#" ONCLICK="datepickerNow(); return false;">Now</A><BR>' . "\n";
|
||||
echo 'Comment: <input id="comment" name="data[Transaction][comment]" type="text" SIZE=80 /><BR>' . "\n";
|
||||
echo $form->end('Post Payment');
|
||||
|
||||
@@ -457,8 +458,12 @@ echo $form->end('Post Payment');
|
||||
<script type="text/javascript">
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#datepicker").datepicker()
|
||||
.datepicker('setDate', '+0');
|
||||
$("#datepicker").datepicker({ constrainInput: true,
|
||||
numberOfMonths: [1, 1],
|
||||
showCurrentAtPos: 0,
|
||||
dateFormat: 'mm/dd/yy' })
|
||||
//.datepicker('setDate', '+0');
|
||||
;
|
||||
|
||||
<?php if (isset($customer['id'])) { ?>
|
||||
$("#customer-id").val(<?php echo $customer['id']; ?>);
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'Account.id', 'formatter' => 'id');
|
||||
$cols['Name'] = array('index' => 'Account.name', 'formatter' => 'name', 'width' => '250');
|
||||
$cols['Type'] = array('index' => 'Account.type', 'width' => '60');
|
||||
$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['Debits'] = array('index' => 'debits', 'formatter' => 'currency');
|
||||
$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency');
|
||||
|
||||
@@ -114,12 +114,12 @@ foreach ($jqGridColumns AS &$col) {
|
||||
elseif ($col['formatter'] === 'currency') {
|
||||
// Switch currency over to our own custom formatting
|
||||
$col['formatter'] = array('--special' => 'currencyFormatter');
|
||||
$default['width'] = 80;
|
||||
$default['width'] = 85;
|
||||
$default['align'] = 'right';
|
||||
}
|
||||
elseif ($col['formatter'] === 'date') {
|
||||
$default['formatoptions'] = array('newformat' => 'm/d/Y');
|
||||
$default['width'] = 90;
|
||||
$default['width'] = 95;
|
||||
$default['align'] = 'center';
|
||||
}
|
||||
elseif ($col['formatter'] === 'name' || $col['formatter'] === 'longname') {
|
||||
|
||||
@@ -12,6 +12,12 @@ if (isset($ledger_id) || isset($account_id) || isset($ar_account)) {
|
||||
$single_amount = true;
|
||||
}
|
||||
|
||||
if (isset($lease_id) || isset($customer_id)) {
|
||||
$references = false;
|
||||
} else {
|
||||
$references = true;
|
||||
}
|
||||
|
||||
if (isset($reconcile_id)) {
|
||||
$applied_amount = true;
|
||||
} else {
|
||||
@@ -47,9 +53,13 @@ else {
|
||||
$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');
|
||||
|
||||
if ($references) {
|
||||
$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);
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
// Define the table columns
|
||||
$cols = array();
|
||||
$cols['ID'] = array('index' => 'id_sequence', 'formatter' => 'id');
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'name', 'width' => '250');
|
||||
$cols['Account'] = array('index' => 'Account.name', 'formatter' => 'longname');
|
||||
$cols['Open Date'] = array('index' => 'Ledger.open_stamp', 'formatter' => 'date');
|
||||
$cols['Close Date'] = array('index' => 'Ledger.close_stamp', 'formatter' => 'date');
|
||||
$cols['Comment'] = array('index' => 'Ledger.comment', 'formatter' => 'comment');
|
||||
$cols['Entries'] = array('index' => 'entries', 'width' => '60', 'align' => 'right');
|
||||
$cols['Debits'] = array('index' => 'debits', 'formatter' => 'currency');
|
||||
$cols['Credits'] = array('index' => 'credits', 'formatter' => 'currency');
|
||||
$cols['Balance'] = array('index' => 'balance', 'formatter' => 'currency');
|
||||
$cols['Close Date'] = array('index' => 'Ledger.close_stamp', 'formatter' => 'date');
|
||||
$cols['Comment'] = array('index' => 'Ledger.comment', 'formatter' => 'comment');
|
||||
|
||||
$jqGrid_options = array('jqGridColumns' => $cols,
|
||||
'controller' => 'ledgers',
|
||||
|
||||
@@ -121,7 +121,6 @@ if ($debit_ledger['Account']['trackable']) {
|
||||
'grid_div_id' => 'debit_reconciliation_ledger_entries',
|
||||
'account_ftype' => 'debit',
|
||||
'reconcile_id' => $entry['id'],
|
||||
//'ledger_entries' => $reconciled['debit']['entry'],
|
||||
));
|
||||
}
|
||||
|
||||
@@ -131,7 +130,6 @@ if ($credit_ledger['Account']['trackable']) {
|
||||
'grid_div_id' => 'credit_reconciliation_ledger_entries',
|
||||
'account_ftype' => 'credit',
|
||||
'reconcile_id' => $entry['id'],
|
||||
//'ledger_entries' => $reconciled['credit']['entry'],
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ $source = $monetarySource['MonetarySource'];
|
||||
$rows = array(array('ID', $source['id']),
|
||||
array('Name', $source['name']),
|
||||
array('Type', $type['name']),
|
||||
array('Data 1', $source['data1']),
|
||||
array('Data 2', $source['data2']),
|
||||
array('Data 3', $source['data3']),
|
||||
array('Data 4', $source['data4']),
|
||||
array('Tillable', $type['tillable'] ? 'Yes' : 'No'),
|
||||
array('Comment', $source['comment']));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user