Realestateunits Documentation
Real Estate Units Controller
File: /controllers/realestateunits.php
Purpose: Comprehensive management system for real estate properties and their individual units
Last Updated: December 20, 2024
Total Functions: 14
Lines of Code: ~372
---
๐ Overview
The Real Estate Units Controller serves as the central management system for real estate properties and their individual units. It provides complete CRUD operations, dynamic form handling, and comprehensive search functionality. This controller handles:
- โข Real estate property creation and management
- โข Individual unit management within properties
- โข Dynamic form addition/removal of units
- โข Advanced search and filtering capabilities
- โข Integration with income and expense type creation
- โข AJAX-powered data tables
- โข Select2 dropdown integration
- โข Comprehensive reporting and display
Primary Functions
- โ Create and edit real estate properties
- โ Manage individual property units
- โ Dynamic unit addition/removal
- โ Advanced search and filtering
- โ AJAX data table management
- โ Select2 dropdown integration
- โ Automatic income/expense type creation
- โ Comprehensive property reporting
- โ Soft delete functionality
- โ Audit trail maintenance
Related Controllers
- โข realestateaveragerevenue.php - Revenue analysis
- โข realestatepayments.php - Payment tracking
- โข incomeTypeController.php - Income type management
- โข expensesTypeController.php - Expense type management
---
๐๏ธ Database Tables
Primary Tables (Direct Operations)
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **realestates** | Main property records | id, realestatename, del, addtoday, adduserid, updatetoday, updateuserid, deltoday, deluserid, savaible, cavaible, incometypeid, expenstypeid | |
| **realestatesunits** | Individual property units | id, realestateid, unitname, unitarea, del, addtoday, adduserid, updatetoday, updateuserid, deltoday, deluserid, cavaible |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **incometype** | Income categories | incomeTypeId, incomeTypeName, incomeTypeDetails | |
| **expensestype** | Expense categories | expensestypeid, expensestypename, expensestypedetails |
| Table Name | Purpose | Key Columns | |
|---|---|---|---|
| **supplier** | Supplier information | supplierid, suppliername, supplierphone, conditions | |
| **client** | Client information | clientid, clientname, clientphone, conditions | |
| **user** | System users | userid, employeename |
๐ Key Functions
1. Default Action - Property Creation Form
Location: Lines 6-10
Purpose: Display the main property creation interface
Process Flow:
1. Display header template
2. Load property creation form (realestateunitsview/add.html)
3. Set realestateunits flag for template
4. Display footer template
---
2. addappend - Dynamic Form Component Addition
Location: Lines 11-18
Purpose: Dynamically add form components (units) to the property form
Function Signature:
// Triggered when: do=addappend (POST)
$itr = filter_input(INPUT_POST, 'itr');
$dataitr = filter_input(INPUT_POST, 'dataitr');
$container = filter_input(INPUT_POST, 'container');
Process Flow:
1. Extract iteration parameters for dynamic form generation
2. Assign iterator values to template
3. Set realestateunits flag
4. Display specified container template (e.g., unit row)
---
3. show - Properties Listing Interface
Location: Lines 19-23
Purpose: Display the main properties listing and search interface
Process Flow:
1. Display header template
2. Load properties listing view (realestateunitsview/show.html)
3. Set realestateunits flag
4. Display footer template
---
4. edit - Property Edit Interface
Location: Lines 24-33
Purpose: Display property editing form with existing data
Function Signature:
// Triggered when: do=edit (GET)
$id = filter_input(INPUT_GET, 'id');
Process Flow:
1. Load property data by ID
2. Load all related units for the property
3. Assign edit data and units to template
4. Display edit form (realestateunitsview/edit.html)
$editdata = R::load('realestates', $id);
$realestatesunits = R::findAll('realestatesunits','realestateid = ? and del < 2',[$id]);
$smarty->assign('editdata', $editdata);
$smarty->assign('realestatesunits', $realestatesunits);
---
5. savedata - Property and Units Save Operation
Location: Lines 118-182
Purpose: Save or update property with multiple units in single transaction
Function Signature:
function savedata() {
$realestatename = filter_input(INPUT_POST, 'realestatename');
$realestatesunitsitr = filter_input(INPUT_POST, 'realestatesunitsitr');
$id = filter_input(INPUT_POST, 'id');
}
Process Flow:
1. Property Creation/Update:
if (!$id) {
// Create new property
$realestates = R::dispense('realestates');
$realestates->del = 0;
$realestates->addtoday = $today;
$realestates->adduserid = $userid;
$realestates->savaible = 0;
$realestates->cavaible = 0;
$realestates->incometypeid = 0;
$realestates->expenstypeid = 0;
} else {
// Update existing property
$realestates = R::load('realestates', $id);
$realestates->del = 1; // Mark as updated
$realestates->updatetoday = $today;
$realestates->updateuserid = $userid;
// Update related income and expense types
R::exec("UPDATE `incometype` SET `incomeTypeName`='$realestatename',
`incomeTypeDetails`='$realestatename' WHERE incomeTypeId = $realestates->incometypeid");
R::exec("UPDATE `expensestype` SET `expensestypename`='$realestatename',
`expensestypedetails`='$realestatename' WHERE expensestypeid = $realestates->expenstypeid");
}
2. Units Processing Loop:
for ($i = 1; $i <= $realestatesunitsitr; $i++) {
$unitname = filter_input(INPUT_POST, 'unitname_' . $i);
$unitarea = filter_input(INPUT_POST, 'unitarea_' . $i);
$unitid = filter_input(INPUT_POST, 'unitid_' . $i);
if (!$unitname) {continue;} // Skip empty units
if (!$unitid) {
// Create new unit
$realestatesunits = R::dispense('realestatesunits');
$realestatesunits->del = 0;
$realestatesunits->addtoday = $today;
$realestatesunits->adduserid = $userid;
} else {
// Update existing unit
$realestatesunits = R::load('realestatesunits', $unitid);
$realestatesunits->del = 1; // Mark as updated
$realestatesunits->updatetoday = $today;
$realestatesunits->updateuserid = $userid;
}
$realestatesunits->realestateid = $realestateid;
$realestatesunits->unitname = $unitname;
$realestatesunits->unitarea = $unitarea;
$realestatesunits->cavaible = 0;
R::store($realestatesunits);
}
3. Income/Expense Type Integration:
if (!$id) {
incomeexpenstype($realestateid, $realestatename);
}
---
6. incomeexpenstype - Automatic Type Creation
Location: Lines 185-198
Purpose: Automatically create income and expense types for new properties
Function Signature:
function incomeexpenstype($realestateid, $realestatename) {
Process Flow:
1. Income Type Creation via CURL:
$send_data = array(
'parent' => 0,
'name' => $realestatename,
'defaultValue' => 0,
'descripe' => $realestatename,
'curlpost' => 1
);
$incometypeid = CURL_IT2($send_data, 'incomeTypeController.php?do=add');
if ($incometypeid > 0) {
R::exec("UPDATE `realestates` SET `incometypeid`= $incometypeid WHERE id = '" . $realestateid . "'");
}
2. Expense Type Creation via CURL:
$send_data = array(
'parent' => 0,
'treeType' => 0,
'name' => $realestatename,
'type' => 0,
'saveid' => 0,
'defaultValue' => 0,
'descripe' => $realestatename,
'curlpost' => 1
);
$expenstypeid = CURL_IT2($send_data, 'expensesTypeController.php?do=add');
if ($expenstypeid > 0) {
R::exec("UPDATE `realestates` SET `expenstypeid`= $expenstypeid WHERE id = '" . $realestateid . "'");
}
---
7. showajax - AJAX Data Table Provider
Location: Lines 209-305
Purpose: Provide server-side data for advanced DataTables with search/filter
Function Signature:
function showajax() {
$columns = array('realestates.id', 'realestatename', '', 'realestates.addtoday', 'employeename', 'realestates.id', 'realestates.id');
}
Process Flow:
1. Filter Processing:
$start_date = filter_input(INPUT_POST, 'start_date');
$end_date = filter_input(INPUT_POST, 'end_date');
$del = filter_input(INPUT_POST, 'del');
$realestateid = filter_input(INPUT_POST, 'realestateid');
$realestateunitid = filter_input(INPUT_POST, 'realestateunitid');
$savaible = filter_input(INPUT_POST, 'savaible');
$cavaible = filter_input(INPUT_POST, 'cavaible');
2. Dynamic Query Building:
$searchQuery = " ";
$searchjoin = " ";
if($realestateid != ''){
$searchQuery .= " and realestates.id = ".$realestateid. " ";
}
if($del == ''){
$searchQuery .= " and realestates.del < 2 ";
}
if ($savaible) {
$searchQuery .= " and realestates.savaible = ".$savaible. " ";
}
if ($cavaible) {
$searchQuery .= " and realestates.cavaible = ".$cavaible. " ";
}
if($realestateunitid != ''){
$searchQuery .= " and realestatesunits.id = ".$realestateunitid. " ";
$searchjoin .= " LEFT JOIN realestatesunits ON realestates.id = realestatesunits.realestateid ";
}
3. Search Functionality:
if (isset($_POST['search']['value']) && $_POST['search']['value'] != "") {
$searchQuery .= "and ( realestates.id LIKE '%".$_POST["search"]["value"]."%'
OR realestates.realestatename LIKE '%".$_POST["search"]["value"]."%'
OR realestates.addtoday LIKE '%".$_POST["search"]["value"]."%'
OR employeename LIKE '%".$_POST["search"]["value"]."%'
)";
}
4. Units Aggregation:
foreach ($rResult as $row) {
$realestatesunits = R::findAll('realestatesunits',"realestateid = ? and del < 2 ",[$row['id']]);
$allunits = '';
foreach($realestatesunits as $units){
$allunits .= $units->unitname . ' / ';
}
// Add to sub_array[2]
}
---
8. removecontroller - Soft Delete Property
Location: Lines 309-325
Purpose: Soft delete property and cascade to all units
Function Signature:
function removecontroller() {
$id = filter_input(INPUT_POST, 'id');
}
Process Flow:
$tables = R::load('realestates', $id);
$tables->del = 2; // Soft delete
$tables->deltoday = $today;
$tables->deluserid = $userid;
try {
R::store($tables);
// Cascade delete to all units
R::exec("UPDATE `realestatesunits` SET `del`= 3 WHERE realestateid = '" . $id . "'");
echo 1; // Success
} catch (Exception $e) {
echo 0; // Failure
}
---
9. Select2 Integration Functions - Dynamic Dropdown Data
select2supplier (Lines 53-65)
function select2supplier() {
$name = $_POST['searchTerm'];
$productsData = R::getAll("SELECT supplierid, CONCAT(suppliername,'/',supplierphone) as texts
FROM supplier WHERE conditions = 0 and CONCAT(suppliername,'/',supplierphone)
LIKE '%" . $name . "%' limit 50");
$return_arr = array();
foreach ($productsData as $pro) {
$row_array = array();
$row_array['id'] = $pro['supplierid'];
$row_array['text'] = $pro['texts'];
array_push($return_arr, $row_array);
}
echo json_encode($return_arr);
}
select2client (Lines 68-80)
function select2client() {
$name = $_POST['searchTerm'];
$productsData = R::getAll("SELECT clientid, CONCAT(clientname,'/',clientphone) as texts
FROM client WHERE conditions = 0 and CONCAT(clientname,'/',clientphone)
LIKE '%" . $name . "%' limit 50");
// Similar structure to select2supplier
}
select2realestates (Lines 84-98)
function select2realestates() {
$name = $_POST['searchTerm'];
$productsData = R::getAll("SELECT id, realestatename
FROM realestates WHERE del < 2 and realestatename LIKE '%" . $name . "%' limit 50");
// Returns property list for dropdown selection
}
select2realestatesunits (Lines 100-115)
function select2realestatesunits() {
$name = $_POST['searchTerm'];
$realestateid = $_POST['realestateid'];
$productsData = R::getAll("SELECT id, CONCAT(unitname,'/',unitarea) as texts
FROM realestatesunits WHERE del < 2 and CONCAT(unitname,'/',unitarea)
LIKE '%" . $name . "%' and realestatesunits.realestateid = $realestateid limit 50");
// Returns units filtered by parent property
}
---
10. getexpenstype - Expense Type Retrieval
Location: Lines 201-205
Purpose: Get expense type ID for a property
Function Signature:
function getexpenstype() {
$id = filter_input(INPUT_POST, 'id');
$realestates = R::load('realestates', $id);
echo $realestates->expenstypeid;
}
---
11. CURL_IT2 - Internal API Communication
Location: Lines 347-371
Purpose: Internal CURL function for controller-to-controller communication
Function Signature:
function CURL_IT2($data_arr = array(), $url) {
$url = 'http://' . $_SERVER['HTTP_HOST'] . explode('controllers', $_SERVER['REQUEST_URI'])[0] . 'controllers/' . $url;
}
Process Flow:
1. Build internal URL from current request
2. Add session data to request
3. Execute CURL POST request
4. Return response for income/expense type creation
---
๐ Workflows
Workflow 1: Complete Property Creation
---
Workflow 2: Property Editing with Units
---
๐ URL Routes & Actions
| URL Parameter | Function Called | Description | |
|---|---|---|---|
| `do=` (empty) | Default action | Property creation form | |
| `do=addappend` | addappend() | Dynamic form component addition | |
| `do=show` | show action | Properties listing interface | |
| `do=edit` | edit action | Property editing form | |
| `do=savedata` | savedata() | Save/update property and units | |
| `do=showajax` | showajax() | AJAX data table provider | |
| `do=removecontroller` | removecontroller() | Soft delete property | |
| `do=select2client` | select2client() | Client dropdown data | |
| `do=select2supplier` | select2supplier() | Supplier dropdown data | |
| `do=select2realestates` | select2realestates() | Properties dropdown data | |
| `do=select2realestatesunits` | select2realestatesunits() | Units dropdown data | |
| `do=getexpenstype` | getexpenstype() | Get expense type ID |
Property Creation (do=savedata, new):
- โข
realestatename- Property name - โข
realestatesunitsitr- Number of units - โข
unitname_{i}- Unit names (1 to N) - โข
unitarea_{i}- Unit areas (1 to N)
Property Edit (do=savedata, update):
- โข
id- Property ID - โข
realestatename- Updated property name - โข
realestatesunitsitr- Number of units - โข
unitname_{i}- Unit names - โข
unitarea_{i}- Unit areas - โข
unitid_{i}- Unit IDs (for existing units)
Dynamic Addition (do=addappend):
- โข
itr- Current iteration number - โข
dataitr- Data iteration parameter - โข
container- Template container to load
AJAX Table (do=showajax):
- โข Standard DataTables parameters (draw, start, length, search, order)
- โข Optional filters: start_date, end_date, del, realestateid, etc.
---
๐งฎ Calculation Methods
Dynamic Unit Management
// Loop through all submitted units
for ($i = 1; $i <= $realestatesunitsitr; $i++) {
$unitname = filter_input(INPUT_POST, 'unitname_' . $i);
$unitarea = filter_input(INPUT_POST, 'unitarea_' . $i);
$unitid = filter_input(INPUT_POST, 'unitid_' . $i);
if (!$unitname) {continue;} // Skip empty units
// Create or update logic based on unitid presence
}
Audit Trail Management
// For new records
$record->del = 0; // Active status
$record->addtoday = $today; // Creation timestamp
$record->adduserid = $userid; // Creator ID
// For updates
$record->del = 1; // Updated status
$record->updatetoday = $today; // Update timestamp
$record->updateuserid = $userid; // Updater ID
// For soft deletes
$record->del = 2; // Deleted status (properties)
$record->del = 3; // Deleted status (units, cascade)
$record->deltoday = $today; // Deletion timestamp
$record->deluserid = $userid; // Deleter ID
Search Query Building
$searchQuery = " ";
if($realestateid != ''){
$searchQuery .= " and realestates.id = ".$realestateid. " ";
}
if($del == ''){
$searchQuery .= " and realestates.del < 2 "; // Active records only
}
---
๐ Security & Permissions
Input Validation
// All inputs filtered through PHP filter_input
$realestatename = filter_input(INPUT_POST, 'realestatename');
$realestatesunitsitr = filter_input(INPUT_POST, 'realestatesunitsitr');
$id = filter_input(INPUT_POST, 'id');
// Dynamic unit inputs validated in loop
for ($i = 1; $i <= $realestatesunitsitr; $i++) {
$unitname = filter_input(INPUT_POST, 'unitname_' . $i);
$unitarea = filter_input(INPUT_POST, 'unitarea_' . $i);
}
SQL Injection Prevention
- โข RedBeanPHP ORM with parameterized queries
- โข Proper integer casting for IDs
- โข Safe string concatenation for search terms
Transaction Safety
try {
$realestateid = R::store($realestates);
// Process all units...
echo $realestateid; // Success
} catch (Exception $e) {
echo 0; // Failure - transaction rolled back
}
Soft Delete Protection
// Only show active records
$searchQuery .= " and realestates.del < 2 ";
// Cascade soft delete to units
R::exec("UPDATE `realestatesunits` SET `del`= 3 WHERE realestateid = '" . $id . "'");
---
๐ Performance Considerations
Database Optimization
1. Critical Indexes:
- realestates(del, id)
- realestatesunits(realestateid, del)
- realestates(addtoday) for date filtering
- realestates(realestatename) for searching
2. Query Efficiency:
- Efficient JOIN usage in showajax
- Proper LIMIT clauses for Select2 dropdowns
- Conditional query building to avoid unnecessary WHERE clauses
3. Memory Management:
- Iterative unit processing instead of bulk loading
- Efficient template variable assignment
- Clean JSON encoding for AJAX responses
AJAX Performance
// Limit Select2 results for performance
"SELECT ... LIMIT 50"
// Efficient DataTables pagination
if (isset($_POST['start']) && $_POST['length'] != '-1') {
$searchQuery .= "LIMIT " . intval($_POST['start']) . ", " . intval($_POST['length']);
}
---
๐ Common Issues & Troubleshooting
1. Units Not Saving
Issue: Units disappear after property save
Cause: Empty unitname causing continue in loop
Debug:
// Add debugging to savedata function
for ($i = 1; $i <= $realestatesunitsitr; $i++) {
$unitname = filter_input(INPUT_POST, 'unitname_' . $i);
echo "Processing unit $i: $unitname<br>";
if (!$unitname) {
echo "Skipping empty unit $i<br>";
continue;
}
}
2. Income/Expense Types Not Created
Issue: New properties don't get associated income/expense types
Cause: CURL_IT2 function failing or endpoint issues
Debug:
// Check CURL responses
$incometypeid = CURL_IT2($send_data, 'incomeTypeController.php?do=add');
echo "Income type ID: " . $incometypeid;
if ($incometypeid <= 0) {
echo "Income type creation failed";
}
3. AJAX Table Not Loading
Issue: DataTable shows "No data available"
Cause: showajax query issues or wrong column count
Debug:
// Add debugging to showajax
$totals = R::count('realestates','LEFT JOIN user ON realestates.adduserid = user.userid
'.$searchjoin.' WHERE 1 '.$searchQuery.' ');
echo "Total records: " . $totals;
// Check column count matches JavaScript
$columns = array('realestates.id', 'realestatename', '', 'realestates.addtoday', 'employeename', 'realestates.id', 'realestates.id');
echo "Column count: " . count($columns);
4. Select2 Dropdowns Not Populating
Issue: Dropdowns show "No results found"
Cause: Search term handling or conditions filtering
Debug:
-- Test supplier search directly
SELECT supplierid, CONCAT(suppliername,'/',supplierphone) as texts
FROM supplier
WHERE conditions = 0
AND CONCAT(suppliername,'/',supplierphone) LIKE '%test%'
LIMIT 50;
---
๐งช Testing Scenarios
Test Case 1: Complete Property Creation
1. Navigate to property creation form
2. Enter property name
3. Add 3 units dynamically using addappend
4. Fill unit names and areas
5. Submit form
6. Verify property and units saved correctly
7. Check income/expense types created
8. Verify audit trail fields populated
Test Case 2: Property Edit with Unit Changes
1. Load existing property with 2 units
2. Edit property name
3. Modify existing unit details
4. Add 1 new unit
5. Submit changes
6. Verify property name updated
7. Verify existing units modified
8. Verify new unit created
9. Check income/expense types updated
Test Case 3: AJAX Table Functionality
1. Create multiple properties with different names/dates
2. Load properties listing page
3. Test search functionality
4. Test date range filtering
5. Test sorting by different columns
6. Test pagination
7. Verify edit/delete buttons work
Test Case 4: Select2 Integration
1. Test supplier dropdown search
2. Test client dropdown search
3. Test properties dropdown search
4. Test units dropdown (with parent property filter)
5. Verify JSON responses are properly formatted
6. Test with special characters in search terms
Test Case 5: Soft Delete Functionality
1. Create property with multiple units
2. Soft delete the property
3. Verify property marked as del = 2
4. Verify all units marked as del = 3
5. Verify property doesn't appear in listings
6. Verify audit trail maintained
---
๐ Related Documentation
- โข CLAUDE.md - PHP 8.2 migration guide
- โข realestateaveragerevenue.md - Revenue analysis
- โข realestatepayments.md - Payment tracking
- โข Database Schema Documentation - Table relationships
- โข Select2 Documentation - Dropdown component
- โข DataTables Documentation - Table component
---
Key Features Summary:
1. Complete CRUD Operations - Create, read, update, soft delete
2. Dynamic Unit Management - Add/remove units on the fly
3. AJAX Integration - Server-side DataTables and Select2
4. Automatic Type Creation - Income/expense types via CURL
5. Comprehensive Audit Trail - Full change tracking
6. Advanced Search/Filter - Multiple criteria support
7. Soft Delete Protection - Data preservation with del flags
8. Transaction Safety - Exception handling and rollback
---
Documented By: AI Assistant
Review Status: โ Complete
Next Review: When major changes occur