| Server IP : 72.60.21.38 / Your IP : 216.73.216.25 Web Server : LiteSpeed System : Linux uk-fast-web1372.main-hosting.eu 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64 User : u390967363 ( 390967363) PHP Version : 8.2.30 Disable Function : system, exec, shell_exec, passthru, mysql_list_dbs, ini_alter, dl, symlink, link, chgrp, leak, popen, apache_child_terminate, virtual, mb_send_mail MySQL : OFF | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/u390967363/domains/aibenproperties.com/public_html/app/ |
Upload File : |
<?php
if (session_status() === PHP_SESSION_NONE) { session_start(); }
ob_start();
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/functions.php';
@require_once __DIR__ . '/includes/mailer.php';
global $pdo;
$companyId = function_exists('getCurrentCompanyId') ? getCurrentCompanyId() : 0;
$role = strtolower($_SESSION['user_role'] ?? '');
$userId = (int)($_SESSION['user_id'] ?? 0);
$success_msg = $_SESSION['deal_success'] ?? '';
if ($success_msg !== '') { unset($_SESSION['deal_success']); }
$clientAccessSetup = $_SESSION['client_access_setup'] ?? null;
if (!empty($clientAccessSetup)) { unset($_SESSION['client_access_setup']); }
$error_msg = '';
// Variant: render differences for Contact Centre vs Marketing pages
$variant = strtolower($_GET['variant'] ?? ((strpos($role, 'contact') !== false || strpos($role, 'customer') !== false) ? 'contact' : 'marketing'));
$isAjax = ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['xhr']) && $_POST['xhr'] === '1');
if (isset($_GET['action']) && is_string($_GET['action'])) {
$act = trim((string)$_GET['action']);
if ($act === 'picker_properties') {
header('Content-Type: application/json; charset=utf-8');
$out = ['ok' => false, 'rows' => []];
try {
$companyIdPicker = function_exists('getCurrentCompanyId') ? (int)getCurrentCompanyId() : 0;
$q = trim((string)($_GET['search'] ?? ''));
$hasProps = $pdo->query("SHOW TABLES LIKE 'properties'")->rowCount() > 0;
if ($hasProps) {
$sel = "p.id, p.title, p.location, p.address, p.price, p.status";
if (function_exists('tableHasColumn') && tableHasColumn('properties','area_sqm')) { $sel .= ", p.area_sqm"; } else { $sel .= ", NULL AS area_sqm"; }
if (function_exists('tableHasColumn') && tableHasColumn('properties','total_sqm')) { $sel .= ", p.total_sqm"; } else { $sel .= ", NULL AS total_sqm"; }
$sql = "SELECT $sel FROM properties p JOIN (SELECT MAX(id) AS id FROM properties GROUP BY LOWER(TRIM(title))) t ON t.id = p.id";
$where = [];
$params = [];
$rolePicker = strtolower((string)($_SESSION['user_role'] ?? 'guest'));
$isPrivilegedPicker = in_array($rolePicker, ['super_admin', 'marketing', 'agent', 'sales', 'sales_agent', 'chairman_ceo', 'executive']);
if (function_exists('tableHasColumn') && tableHasColumn('properties','is_active')) { $where[] = "p.is_active = 1"; }
elseif (function_exists('tableHasColumn') && tableHasColumn('properties','status')) { $where[] = "(p.status IS NULL OR p.status <> 'archived')"; }
if (!$isPrivilegedPicker && $companyIdPicker && function_exists('tableHasColumn') && tableHasColumn('properties','company_id')) {
$where[] = "(p.company_id = ? OR p.company_id IS NULL OR p.company_id = 0)";
$params[] = $companyIdPicker;
}
if ($q !== '') { $where[] = "(p.title LIKE ? OR p.location LIKE ? OR p.address LIKE ?)"; $params[] = '%' . $q . '%'; $params[] = '%' . $q . '%'; $params[] = '%' . $q . '%'; }
if ($where) { $sql .= " WHERE " . implode(" AND ", $where); }
$sql .= " ORDER BY p.sort_order ASC, p.id DESC LIMIT 400";
$st = $pdo->prepare($sql);
$st->execute($params);
$out['rows'] = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
$out['ok'] = true;
} catch (Throwable $e) {
$out = ['ok' => false, 'rows' => [], 'error' => 'server_error'];
}
echo json_encode($out);
exit;
}
if ($act === 'balances') {
header('Content-Type: application/json; charset=utf-8');
$out = [
'ok' => false,
'property_id' => 0,
'client_id' => 0,
'land' => ['total' => 0.0, 'paid' => 0.0, 'balance' => 0.0],
'infrastructure' => ['total' => 0.0, 'paid' => 0.0, 'balance' => 0.0],
'excavation' => ['total' => 0.0, 'paid' => 0.0, 'balance' => 0.0],
'accounts' => ['land' => false, 'infrastructure' => false, 'excavation' => false],
];
try {
$propertyIdB = isset($_GET['property_id']) ? (int)$_GET['property_id'] : 0;
$clientIdB = isset($_GET['client_id']) ? (int)$_GET['client_id'] : 0;
$companyIdB = function_exists('getCurrentCompanyId') ? (int)getCurrentCompanyId() : 0;
$out['property_id'] = $propertyIdB;
$out['client_id'] = $clientIdB;
if ($propertyIdB > 0) {
try {
$hasAcc = $pdo->query("SHOW TABLES LIKE 'accounts'")->rowCount() > 0;
if ($hasAcc && function_exists('tableHasColumn') && tableHasColumn('accounts', 'property_id') && tableHasColumn('accounts', 'account_type') && tableHasColumn('accounts', 'is_active')) {
$q = "SELECT account_type FROM accounts WHERE property_id = ? AND is_active = 1 AND account_type IN ('land','infrastructure','excavation')";
$params = [$propertyIdB];
if ($companyIdB && tableHasColumn('accounts','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyIdB; }
$st = $pdo->prepare($q);
$st->execute($params);
foreach (($st->fetchAll(PDO::FETCH_COLUMN) ?: []) as $tRaw) {
$t = strtolower(trim((string)$tRaw));
if (isset($out['accounts'][$t])) { $out['accounts'][$t] = true; }
}
}
} catch (Throwable $e) {}
}
if ($propertyIdB > 0 && $clientIdB > 0) {
$finalStatuses = function_exists('kpiPaymentFinalizedStatuses') ? (array)kpiPaymentFinalizedStatuses() : ['verified','approved','paid','completed','success'];
$statusSql = function_exists('kpiSqlList') ? kpiSqlList($finalStatuses) : "('verified','approved','paid','completed','success')";
$payCompany = ($companyIdB && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) ? " AND (company_id = " . (int)$companyIdB . " OR company_id IS NULL)" : "";
$landTotal = 0.0;
$landDiscount = 0.0;
try {
$hasDS = $pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0;
if ($hasDS && function_exists('hasColumn') && hasColumn($pdo,'deals_submit','meta_json')) {
$qds = $pdo->prepare("SELECT meta_json FROM deals_submit WHERE user_id = ?" . ($companyIdB && function_exists('hasColumn') && hasColumn($pdo,'deals_submit','company_id') ? " AND (company_id = " . (int)$companyIdB . " OR company_id IS NULL)" : "") . " ORDER BY created_at DESC LIMIT 50");
$qds->execute([$clientIdB]);
$rows = $qds->fetchAll(PDO::FETCH_COLUMN) ?: [];
foreach ($rows as $mjRaw) {
if (!$mjRaw) continue;
$mj = json_decode((string)$mjRaw, true);
if (!is_array($mj)) continue;
if ((int)($mj['property_id'] ?? 0) !== $propertyIdB) continue;
$landTotal = (float)($mj['amount_offered'] ?? 0);
$landDiscount = (float)($mj['discount_amount'] ?? 0);
break;
}
}
} catch (Throwable $e) {}
$landDue = max(0.0, $landTotal - $landDiscount);
$landPaid = 0.0;
try {
$typeClause = (function_exists('tableHasColumn') && tableHasColumn('payments','payment_type')) ? " AND LOWER(TRIM(payment_type)) = 'land'" : "";
$propClause = (function_exists('tableHasColumn') && tableHasColumn('payments','property_id')) ? " AND property_id = " . (int)$propertyIdB : "";
$userWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','user_id')) ? "user_id = " . (int)$clientIdB : "1=0";
$clientWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? "client_id = " . (int)$clientIdB : "1=0";
$landPaid = (float)$pdo->query("SELECT COALESCE(SUM(amount),0) FROM payments WHERE (" . $userWhere . " OR " . $clientWhere . ") AND status IN {$statusSql}{$payCompany}{$propClause}{$typeClause}")->fetchColumn();
} catch (Throwable $e) { $landPaid = 0.0; }
$chargeTotals = ['infrastructure' => 0.0, 'excavation' => 0.0];
try {
$hasPc = $pdo->query("SHOW TABLES LIKE 'property_charges'")->rowCount() > 0;
if ($hasPc) {
$q = "SELECT charge_name, amount FROM property_charges WHERE property_id = ? AND is_active = 1";
$params = [$propertyIdB];
if ($companyIdB && function_exists('tableHasColumn') && tableHasColumn('property_charges','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL OR company_id = 0)"; $params[] = $companyIdB; }
$st = $pdo->prepare($q);
$st->execute($params);
$rows = $st->fetchAll(PDO::FETCH_ASSOC) ?: [];
foreach ($rows as $r) {
$nm = strtolower(trim((string)($r['charge_name'] ?? '')));
$amt = (float)($r['amount'] ?? 0);
if (!is_finite($amt) || $amt < 0) $amt = 0.0;
if ($nm === '') continue;
if (strpos($nm, 'infra') !== false || strpos($nm, 'infrastructure') !== false) { $chargeTotals['infrastructure'] += $amt; }
if (strpos($nm, 'excav') !== false || strpos($nm, 'excavation') !== false || strpos($nm, 'escav') !== false) { $chargeTotals['excavation'] += $amt; }
}
}
} catch (Throwable $e) {}
$paidByType = ['infrastructure' => 0.0, 'excavation' => 0.0];
try {
$propClause = (function_exists('tableHasColumn') && tableHasColumn('payments','property_id')) ? " AND property_id = " . (int)$propertyIdB : "";
$userWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','user_id')) ? "user_id = " . (int)$clientIdB : "1=0";
$clientWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? "client_id = " . (int)$clientIdB : "1=0";
$typeCol = (function_exists('tableHasColumn') && tableHasColumn('payments','payment_type')) ? "LOWER(TRIM(payment_type))" : "''";
$st = $pdo->query("SELECT {$typeCol} AS t, COALESCE(SUM(amount),0) AS s FROM payments WHERE (" . $userWhere . " OR " . $clientWhere . ") AND status IN {$statusSql}{$payCompany}{$propClause} GROUP BY {$typeCol}");
$rows = $st ? ($st->fetchAll(PDO::FETCH_ASSOC) ?: []) : [];
foreach ($rows as $r) {
$t = strtolower(trim((string)($r['t'] ?? '')));
$s = (float)($r['s'] ?? 0);
if ($t === 'infrastructure' || $t === 'infra') { $paidByType['infrastructure'] += $s; }
if ($t === 'excavation' || $t === 'excav') { $paidByType['excavation'] += $s; }
}
} catch (Throwable $e) {}
$out['land'] = ['total' => $landDue, 'paid' => $landPaid, 'balance' => max(0.0, $landDue - $landPaid)];
$out['infrastructure'] = ['total' => $chargeTotals['infrastructure'], 'paid' => $paidByType['infrastructure'], 'balance' => max(0.0, $chargeTotals['infrastructure'] - $paidByType['infrastructure'])];
$out['excavation'] = ['total' => $chargeTotals['excavation'], 'paid' => $paidByType['excavation'], 'balance' => max(0.0, $chargeTotals['excavation'] - $paidByType['excavation'])];
}
$out['ok'] = true;
} catch (Throwable $e) {
$out = ['ok' => false, 'error' => 'server_error'];
}
echo json_encode($out);
exit;
}
}
if (!$isAjax) { include __DIR__ . '/includes/header.php'; }
if ($isAjax && !isset($_POST['submit_deal'])) { $_POST['submit_deal'] = '1'; }
// Ensure schema for deals table and payments.deal_id linkage (skip during AJAX POST for responsiveness)
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
try {
if (function_exists('ensurePropertyAccountsTable')) { ensurePropertyAccountsTable(); }
if (function_exists('ensurePropertyExpensesTable')) { ensurePropertyExpensesTable(); }
if (function_exists('ensurePropertyPaymentsColumns')) { ensurePropertyPaymentsColumns(); }
$hasDeals = $pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0;
if (!$hasDeals) {
$pdo->exec("
CREATE TABLE IF NOT EXISTS deals (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
marketer_id INT NULL,
marketer_name VARCHAR(255) NULL,
deal_source VARCHAR(100) NULL,
project_desc VARCHAR(255) NULL,
project_name VARCHAR(255) NULL,
payment_plan VARCHAR(50) NULL,
amount_offered DECIMAL(14,2) NULL,
amount_paid_so_far DECIMAL(14,2) NULL,
discount_amount DECIMAL(14,2) NULL,
discount_approved_by VARCHAR(120) NULL,
commission_percent DECIMAL(6,2) NULL,
marketer_commission DECIMAL(14,2) NULL,
agent_commission DECIMAL(14,2) NULL,
balance_remaining DECIMAL(14,2) NULL,
marketer_bank JSON NULL,
agent_bank JSON NULL,
notes TEXT NULL,
receipt_file VARCHAR(255) NULL,
status VARCHAR(50) NULL,
submitted_by INT NULL,
company_id INT NULL,
meta_json JSON NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
}
$hasDealsSubmit = $pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0;
if (!$hasDealsSubmit) {
$pdo->exec("
CREATE TABLE IF NOT EXISTS deals_submit (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
marketer_id INT NULL,
marketer_name VARCHAR(255) NULL,
deal_source VARCHAR(100) NULL,
project_desc VARCHAR(255) NULL,
project_name VARCHAR(255) NULL,
payment_plan VARCHAR(50) NULL,
installment_start_date DATE NULL,
policy_accepted TINYINT(1) DEFAULT 0,
amount_offered DECIMAL(14,2) NULL,
amount_paid_so_far DECIMAL(14,2) NULL,
discount_amount DECIMAL(14,2) NULL,
discount_approved_by VARCHAR(120) NULL,
commission_percent DECIMAL(6,2) NULL,
marketer_commission DECIMAL(14,2) NULL,
agent_commission DECIMAL(14,2) NULL,
balance_remaining DECIMAL(14,2) NULL,
marketer_bank JSON NULL,
agent_bank JSON NULL,
notes TEXT NULL,
receipt_file VARCHAR(255) NULL,
status VARCHAR(50) NULL,
submitted_by INT NULL,
company_id INT NULL,
meta_json JSON NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
");
}
if (function_exists('tableHasColumn') && !tableHasColumn('payments','deal_id')) {
$pdo->exec("ALTER TABLE payments ADD COLUMN deal_id INT NULL");
}
} catch (Throwable $eSchema) {}
}
function safeUploadReceipt($file, string $dir = 'uploads/deals') {
try {
if (!is_array($file) || !isset($file['error']) || $file['error'] !== UPLOAD_ERR_OK) return '';
if (!isset($file['tmp_name']) || !is_string($file['tmp_name']) || $file['tmp_name'] === '') return '';
if (!isset($file['name']) || !is_string($file['name']) || $file['name'] === '') return '';
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if (!is_dir($dir)) { @mkdir($dir, 0777, true); }
$name = $dir . '/deal_receipt_' . date('Ymd_His') . '_' . mt_rand(1000,9999) . '.' . $ext;
if (move_uploaded_file($file['tmp_name'], $name)) {
global $pdo;
if (function_exists('tableHasColumn') && tableHasColumn('payments','proof_file')) {
try {
$chk = $pdo->prepare("SELECT id FROM payments WHERE proof_file = ? AND status <> 'rejected' LIMIT 1");
$chk->execute([$name]);
if ($chk->fetchColumn()) {
return $name;
}
} catch (Throwable $e) {}
}
return $name;
}
} catch (Throwable $e) {}
return '';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_deal'])) {
try {
try {
$env = strtolower((string)(getenv('APP_ENV') ?: ($_ENV['APP_ENV'] ?? '')));
$dbg = strtolower((string)(getenv('APP_DEBUG') ?: ($_ENV['APP_DEBUG'] ?? '')));
$isLocal = ($env === 'local') || ($env === 'development') || (strpos((string)($_SERVER['HTTP_HOST'] ?? ''), 'localhost') !== false);
if ($isLocal && ($dbg === '1' || $dbg === 'true' || $dbg === 'yes')) {
@file_put_contents(__DIR__ . '/debug_form.txt', print_r($_POST, true));
}
} catch (Throwable $eLog) {}
$client_id = (int)($_POST['client_id'] ?? 0);
$client_name = trim($_POST['client_name'] ?? '');
$client_email = trim($_POST['client_email'] ?? '');
$project_name = trim($_POST['project_name'] ?? '');
$project_desc = trim($_POST['project_desc'] ?? '');
$property_id = (int)($_POST['property_id'] ?? 0);
$payment_type = strtolower(trim((string)($_POST['payment_type'] ?? 'land')));
if (!in_array($payment_type, ['land','infrastructure','excavation'], true)) { $payment_type = 'land'; }
$deal_source = trim($_POST['deal_source'] ?? '');
$plan_type = trim($_POST['plan_type'] ?? 'full');
$custom_months = (int)($_POST['custom_months'] ?? 0);
$installment_start_date = trim($_POST['installment_start_date'] ?? '');
$sqm = isset($_POST['sqm']) ? (float)str_replace(',', '', (string)$_POST['sqm']) : 0.0;
$policy_accepted = isset($_POST['policy_acceptance']) ? 1 : 0;
$amount_offered = (float)str_replace(',', '', $_POST['amount_offered'] ?? '0');
$amount_paid_so_far = (float)str_replace(',', '', $_POST['amount_paid_so_far'] ?? '0');
$commission_pct = (float)($_POST['commission_pct'] ?? 5.0);
$marketer_pct = (float)($_POST['marketer_pct'] ?? 3.0);
$agent_pct = (float)($_POST['agent_pct'] ?? 2.0);
$marketer_comm = (float)str_replace(',', '', $_POST['marketer_comm'] ?? '0');
$agent_comm = (float)str_replace(',', '', $_POST['agent_comm'] ?? '0');
$discount_amount = (float)str_replace(',', '', $_POST['discount_amount'] ?? '0');
$discount_approved_by = trim($_POST['discount_approved_by'] ?? '');
$paid_now = (float)str_replace(',', '', $_POST['amount_paid_now'] ?? '0');
$balance_remaining = (float)str_replace(',', '', $_POST['balance_remaining'] ?? '0');
$marketer_name = trim($_POST['marketer_name'] ?? '');
$client_payment_status = trim($_POST['client_payment_status'] ?? '');
$mk_acc_no = trim($_POST['mk_acc_no'] ?? '');
$mk_bank = trim($_POST['mk_bank'] ?? '');
$mk_acc_name = trim($_POST['mk_acc_name'] ?? '');
$ag_acc_no = trim($_POST['ag_acc_no'] ?? '');
$ag_bank = trim($_POST['ag_bank'] ?? '');
$ag_acc_name = trim($_POST['ag_acc_name'] ?? '');
$existing_deal_id = (int)($_POST['existing_deal_id'] ?? 0);
$receipt = safeUploadReceipt($_FILES['receipt'] ?? null);
if (!$receipt) {
$error_msg = 'Please upload the payment receipt before submitting.';
throw new Exception($error_msg);
}
$isPrivilegedSubmitter = in_array($role, ['super_admin','admin','finance','finance_officer','finance_manager','accountant','accounting'], true);
if (!$isPrivilegedSubmitter && $client_id > 0) {
$allowedClient = false;
$companyIdCheck = (int)($companyId ?: 0);
try {
if (function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) {
$q = "SELECT 1 FROM users WHERE id = ? AND role = 'client' AND created_by = ?";
$params = [$client_id, $userId];
if ($companyIdCheck && function_exists('hasColumn') && hasColumn($pdo,'users','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyIdCheck; }
$st = $pdo->prepare($q);
$st->execute($params);
$allowedClient = (bool)$st->fetchColumn();
}
} catch (Throwable $e) {}
if (!$allowedClient) {
try {
if ($pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) {
$w = "submitted_by_user = ?";
$params = [$client_id, $userId];
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $w .= " OR marketer_id = ?"; $params[] = $userId; }
$q = "SELECT 1 FROM deals_submit WHERE user_id = ? AND ($w)";
if ($companyIdCheck && function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyIdCheck; }
$q .= " LIMIT 1";
$st = $pdo->prepare($q);
$st->execute($params);
$allowedClient = (bool)$st->fetchColumn();
}
} catch (Throwable $e) {}
}
if (!$allowedClient) {
try {
if ($pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0 && function_exists('tableHasColumn') && tableHasColumn('payments','submitted_by_user')) {
$clientCol = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? 'client_id' : 'user_id';
$q = "SELECT 1 FROM payments WHERE {$clientCol} = ? AND submitted_by_user = ?";
$params = [$client_id, $userId];
if ($companyIdCheck && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyIdCheck; }
$q .= " LIMIT 1";
$st = $pdo->prepare($q);
$st->execute($params);
$allowedClient = (bool)$st->fetchColumn();
}
} catch (Throwable $e) {}
}
if (!$allowedClient) {
throw new Exception("You can only submit deals for your own clients.");
}
}
$plan_type = strtolower(trim((string)$plan_type));
if ($plan_type === 'custom') {
if ($custom_months <= 0) {
$error_msg = 'Please enter the custom plan duration in months.';
throw new Exception($error_msg);
}
if ($custom_months < 1 || $custom_months > 120) {
$error_msg = 'Custom plan months must be between 1 and 120.';
throw new Exception($error_msg);
}
if ($custom_months === 3) { $plan_type = '3_months'; }
elseif ($custom_months === 6) { $plan_type = '6_months'; }
else { $plan_type = 'custom_' . $custom_months . '_months'; }
}
// Normalize missing marketer_name and deal_source from session and auxiliary fields
try {
if (!in_array($role, ['super_admin','admin'], true) && $marketer_name === '' && isset($_POST['submitted_by_name']) && trim((string)$_POST['submitted_by_name']) !== '') {
$marketer_name = trim((string)$_POST['submitted_by_name']);
}
if ($marketer_name === '' && $userId > 0 && function_exists('tableHasColumn') && tableHasColumn('users','name')) {
$qs = $pdo->prepare("SELECT name FROM users WHERE id = ? LIMIT 1");
$qs->execute([$userId]);
$nm = (string)($qs->fetchColumn() ?: '');
if ($nm !== '' && $role && $role !== 'client') { $marketer_name = $nm; }
}
} catch (Throwable $eNorm1) {}
try {
if ($deal_source === '' && isset($_POST['submitted_by_role']) && trim((string)$_POST['submitted_by_role']) !== '') {
$deal_source = ucfirst(strtolower((string)$_POST['submitted_by_role']));
}
if ($deal_source === '' && $role !== '') { $deal_source = ucfirst(strtolower($role)); }
if ($deal_source === '') { $deal_source = 'Marketing'; }
} catch (Throwable $eNorm2) {}
$submittedByRole = $role;
try {
$dsLower = strtolower(trim((string)$deal_source));
if ($dsLower !== '') {
if (strpos($dsLower, 'contact') !== false) { $submittedByRole = 'contact_centre'; }
elseif (strpos($dsLower, 'marketer') !== false || strpos($dsLower, 'marketing') !== false || strpos($dsLower, 'sales') !== false) { $submittedByRole = 'marketing'; }
elseif (strpos($dsLower, 'client') !== false) { $submittedByRole = 'client'; }
elseif (strpos($dsLower, 'referral') !== false) { $submittedByRole = 'referral'; }
elseif (strpos($dsLower, 'internal') !== false || strpos($dsLower, 'staff') !== false) { $submittedByRole = 'staff'; }
}
} catch (Throwable $eRoleNorm) { $submittedByRole = $role; }
$creditedUserId = $userId;
$creditedByRole = $submittedByRole;
$canCredit = in_array($role, ['super_admin','admin'], true);
try {
if ($canCredit && $marketer_name !== '' && function_exists('tableHasColumn') && tableHasColumn('users','name') && tableHasColumn('users','role')) {
$stC = $pdo->prepare("SELECT id, role FROM users WHERE name = ? LIMIT 1");
$stC->execute([$marketer_name]);
$rowC = $stC->fetch(PDO::FETCH_ASSOC) ?: [];
$cid = (int)($rowC['id'] ?? 0);
$crole = strtolower(trim((string)($rowC['role'] ?? '')));
if ($cid > 0 && $crole !== '' && $crole !== 'client') {
$creditedUserId = $cid;
if (strpos($crole, 'contact') !== false || strpos($crole, 'customer') !== false) { $creditedByRole = 'contact_centre'; }
elseif (in_array($crole, ['marketing','sales','agent','sales_agent'], true)) { $creditedByRole = 'marketing'; }
else { $creditedByRole = $submittedByRole; }
}
}
} catch (Throwable $eCredit) {}
$recordedByUserId = $userId;
$recordedByRole = $role;
$recordedByName = trim((string)($_SESSION['user_name'] ?? $_SESSION['name'] ?? $_SESSION['full_name'] ?? $_SESSION['username'] ?? ''));
$performedByUserId = $userId;
$performedByRole = $submittedByRole;
$performedByName = $recordedByName;
if (!empty($canCredit) && $creditedUserId > 0 && $creditedUserId !== $userId) {
$performedByUserId = $creditedUserId;
$performedByRole = $creditedByRole;
$performedByName = $marketer_name !== '' ? $marketer_name : $performedByName;
}
if ($performedByName === '' && $performedByUserId > 0) {
try {
$cols = [];
if (function_exists('tableHasColumn') && tableHasColumn('users','name')) { $cols[] = 'name'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','full_name')) { $cols[] = 'full_name'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','first_name')) { $cols[] = 'first_name'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','last_name')) { $cols[] = 'last_name'; }
if (!empty($cols)) {
$stU = $pdo->prepare("SELECT " . implode(',', array_unique($cols)) . " FROM users WHERE id = ? LIMIT 1");
$stU->execute([(int)$performedByUserId]);
$urow = $stU->fetch(PDO::FETCH_ASSOC) ?: [];
$nm = trim((string)($urow['name'] ?? ''));
if ($nm === '') { $nm = trim((string)($urow['full_name'] ?? '')); }
if ($nm === '') { $nm = trim((string)((string)($urow['first_name'] ?? '') . ' ' . (string)($urow['last_name'] ?? ''))); }
if ($nm !== '') { $performedByName = $nm; }
}
} catch (Throwable $ePbn) {}
}
if ($performedByName === '') { $performedByName = ucfirst(strtolower((string)$performedByRole)); }
$receipt_hash = '';
$isDuplicate = false;
if ($receipt && file_exists($receipt)) {
try {
$receipt_hash = sha1_file($receipt);
if (tableHasColumn('payments','receipt_hash')) {
$chk = $pdo->prepare("SELECT id FROM payments WHERE receipt_hash = ? AND status <> 'rejected' LIMIT 1");
$chk->execute([$receipt_hash]);
$isDuplicate = (bool)$chk->fetchColumn();
} elseif (tableHasColumn('payments','proof_file')) {
$chk2 = $pdo->prepare("SELECT id FROM payments WHERE proof_file = ? AND status <> 'rejected' LIMIT 1");
$chk2->execute([$receipt]);
$isDuplicate = (bool)$chk2->fetchColumn();
}
} catch (Throwable $e) {}
}
$transactions = [];
if (!empty($_POST['txn_date']) && is_array($_POST['txn_date'])) {
$dates = $_POST['txn_date']; $amounts = $_POST['txn_amount'] ?? [];
foreach ($dates as $i => $d) {
$raw_amount = isset($amounts[$i]) ? (string)$amounts[$i] : '0';
$a = (float)str_replace(',', '', $raw_amount);
if (trim($d) !== '' && $a > 0) $transactions[] = ['date'=>$d,'amount'=>$a];
}
}
$clientId = $client_id;
if (!$clientId && $client_email !== '') {
try {
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ? LIMIT 1");
$stmt->execute([$client_email]);
$clientId = (int)($stmt->fetchColumn() ?: 0);
} catch (Throwable $e) {}
}
if (!$clientId && $client_name !== '') {
try {
$stmt = $pdo->prepare("SELECT id FROM users WHERE role = 'client' AND name = ? LIMIT 1");
$stmt->execute([$client_name]);
$clientId = (int)($stmt->fetchColumn() ?: 0);
} catch (Throwable $e) {}
}
if ($clientId && $client_name === '') {
try {
$stmt = $pdo->prepare("SELECT name FROM users WHERE id = ? LIMIT 1");
$stmt->execute([$clientId]);
$client_name = (string)($stmt->fetchColumn() ?: '');
} catch (Throwable $e) {}
}
$setup_link = ''; $setup_code = '';
$client_created_password = '';
$client_was_created = false;
if (!$clientId && $client_email !== '') {
try {
$pwd = bin2hex(random_bytes(6));
$hash = password_hash($pwd, PASSWORD_BCRYPT);
$cols = []; $vals = [];
if (function_exists('tableHasColumn') && tableHasColumn('users','company_id') && $companyId) { $cols[]='company_id'; $vals[]=$companyId; }
if (function_exists('tableHasColumn') && tableHasColumn('users','name')) { $cols[]='name'; $vals[]= $client_name ?: $client_email; }
if (function_exists('tableHasColumn') && tableHasColumn('users','username') && !tableHasColumn('users','name')) { $cols[]='username'; $vals[]= $client_name ?: $client_email; }
$cols[]='email'; $vals[]=$client_email;
if (function_exists('tableHasColumn') && tableHasColumn('users','password')) { $cols[]='password'; $vals[]=$hash; }
if (function_exists('tableHasColumn') && tableHasColumn('users','role')) { $cols[]='role'; $vals[]='client'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','status')) { $cols[]='status'; $vals[]='registered'; }
$sqlU = "INSERT INTO users (" . implode(',', $cols) . ") VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
$insU = $pdo->prepare($sqlU); $insU->execute($vals);
$clientId = (int)$pdo->lastInsertId();
$client_created_password = $pwd;
$client_was_created = ($clientId > 0);
try {
if (function_exists('tableHasColumn') && tableHasColumn('users','exec_otp_code') && tableHasColumn('users','exec_otp_expires')) {
$code = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
$exp = date('Y-m-d H:i:s', time() + 172800);
$pdo->prepare("UPDATE users SET exec_otp_code = ?, exec_otp_expires = ? WHERE id = ?")->execute([$code, $exp, $clientId]);
$setup_code = $code;
}
} catch (Throwable $e2) {}
try {
$appUrl = function_exists('getSetting') ? getSetting('app_url', '') : '';
if (!$appUrl) { $appUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . rtrim(dirname($_SERVER['PHP_SELF'] ?? '/'), '/'); }
$setup_link = rtrim($appUrl, '/') . '/login.php';
} catch (Throwable $e3) { $setup_link = 'login.php'; }
} catch (Throwable $e) {}
}
if ($plan_type === 'full') {
$amount_paid_so_far = 0.0;
$discount_amount = 0.0;
$balance_remaining = 0.0;
if (!is_finite($amount_offered) || $amount_offered <= 0) { $amount_offered = $paid_now; }
}
if ($payment_type !== 'land') {
$plan_type = 'full';
$custom_months = 0;
$installment_start_date = '';
$amount_offered = 0.0;
$amount_paid_so_far = 0.0;
$discount_amount = 0.0;
$discount_approved_by = '';
$balance_remaining = 0.0;
$commission_pct = 0.0;
$marketer_pct = 0.0;
$agent_pct = 0.0;
$marketer_comm = 0.0;
$agent_comm = 0.0;
}
if (!is_finite($commission_pct) || $commission_pct < 0) $commission_pct = 0.0;
if ($commission_pct > 100.0) $commission_pct = 100.0;
$commission_pct = round($commission_pct, 2);
if ($marketer_pct < 0) $marketer_pct = 0.0; if ($marketer_pct > $commission_pct) $marketer_pct = $commission_pct;
if ($agent_pct < 0) $agent_pct = 0.0; if ($agent_pct > $commission_pct) $agent_pct = $commission_pct;
$sumPct = round($marketer_pct + $agent_pct, 2);
if (abs($sumPct - $commission_pct) > 0.01) {
$agent_pct = round(max(0.0, $commission_pct - $marketer_pct), 2);
}
$txnSum = 0.0;
if (!empty($transactions)) {
foreach ($transactions as $t) {
if (isset($t['amount']) && is_finite((float)$t['amount']) && (float)$t['amount'] > 0) {
$txnSum += (float)$t['amount'];
}
}
}
if ($payment_type === 'land') {
$commissionBase = $paid_now;
if (!is_finite($commissionBase) || $commissionBase <= 0) {
if (is_finite($txnSum) && $txnSum > 0) { $commissionBase = $txnSum; }
elseif (is_finite($amount_paid_so_far) && $amount_paid_so_far > 0) { $commissionBase = $amount_paid_so_far; }
elseif (is_finite($amount_offered) && $amount_offered > 0) { $commissionBase = $amount_offered; }
else { $commissionBase = 0.0; }
}
if ($commissionBase > 0) {
$marketer_comm = ($commissionBase * ($marketer_pct/100));
$agent_comm = ($commissionBase * ($agent_pct/100));
}
}
if ($plan_type !== 'full') {
$discBase = max($amount_offered - $discount_amount, 0);
$computedBalance = max($discBase - ($amount_paid_so_far + $paid_now), 0);
// SERVER-SIDE OVERPAYMENT PROTECTION
if (($amount_paid_so_far + $paid_now) > $discBase && $discBase > 0) {
$error_msg = 'Payment error: Total payments (₦'.number_format($amount_paid_so_far + $paid_now).') exceed the offer amount (₦'.number_format($discBase).').';
throw new Exception($error_msg);
}
if ($balance_remaining <= 0) { $balance_remaining = $computedBalance; }
}
// Prepare a unique reference early so it can be included in meta for Finance views
$uniqRef = date('YmdHis') . '-' . random_int(1000,9999);
$meta = [
'client_id' => $clientId,
'client_name' => $client_name,
'client_email' => $client_email,
'marketer_name' => $marketer_name,
'project_desc' => $project_desc,
'deal_source' => $deal_source,
'property_id' => $property_id,
'payment_type' => $payment_type,
'sqm' => $sqm,
'plan_type' => $plan_type,
'custom_months' => $custom_months,
'installment_start_date' => $installment_start_date,
'policy_accepted' => $policy_accepted,
'amount_offered' => $amount_offered,
'amount_paid_so_far' => $amount_paid_so_far,
'discount_amount' => $discount_amount,
'discount_approved_by' => $discount_approved_by,
'commission_pct' => $commission_pct,
'commission_total_pct' => $commission_pct,
'marketer_pct' => $marketer_pct,
'agent_pct' => $agent_pct,
'marketer_comm' => $marketer_comm,
'agent_comm' => $agent_comm,
'balance_remaining' => $balance_remaining,
'client_payment_status' => $client_payment_status,
'payment_reference' => 'deal-submission-' . $uniqRef,
'payment_method' => 'Bank Transfer',
'marketer_bank' => ['acc_no'=>$mk_acc_no,'bank'=>$mk_bank,'acc_name'=>$mk_acc_name],
'agent_bank' => ['acc_no'=>$ag_acc_no,'bank'=>$ag_bank,'acc_name'=>$ag_acc_name],
'transactions' => $transactions,
'submitted_by_role' => $performedByRole,
'submitted_by_user' => $performedByUserId,
'submitted_by_name' => $performedByName,
'recorded_by_role' => $recordedByRole,
'recorded_by_user' => $recordedByUserId,
'recorded_by_name' => $recordedByName,
'credited_to_role' => $creditedByRole,
'credited_to_user' => $creditedUserId,
'credited_to_name' => $marketer_name,
];
if ($payment_type !== 'land' && $existing_deal_id <= 0) {
$error_msg = 'Please select an existing deal/property record before submitting an ' . ucfirst($payment_type) . ' payment.';
throw new Exception($error_msg);
}
$resolvedDealPropertyId = 0;
if ($existing_deal_id > 0) {
try {
$hasDealsTbl = $pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0;
if ($hasDealsTbl && function_exists('tableHasColumn') && tableHasColumn('deals', 'property_id')) {
$stDp = $pdo->prepare("SELECT property_id FROM deals WHERE id = ? LIMIT 1");
$stDp->execute([$existing_deal_id]);
$resolvedDealPropertyId = (int)($stDp->fetchColumn() ?: 0);
}
} catch (Throwable $e) { $resolvedDealPropertyId = 0; }
if ($resolvedDealPropertyId <= 0) {
try {
$hasDsTbl = $pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0;
if ($hasDsTbl && function_exists('tableHasColumn') && tableHasColumn('deals_submit', 'meta_json')) {
$stMj = $pdo->prepare("SELECT meta_json FROM deals_submit WHERE id = ? LIMIT 1");
$stMj->execute([$existing_deal_id]);
$mjRaw = (string)($stMj->fetchColumn() ?: '');
if ($mjRaw !== '') {
$mj = json_decode($mjRaw, true);
if (is_array($mj)) {
$resolvedDealPropertyId = (int)($mj['property_id'] ?? 0);
}
}
}
} catch (Throwable $e) { $resolvedDealPropertyId = 0; }
}
}
if ($resolvedDealPropertyId > 0 && ($payment_type !== 'land' || $property_id <= 0)) {
$property_id = $resolvedDealPropertyId;
$meta['property_id'] = $resolvedDealPropertyId;
}
$paymentPropertyId = $property_id > 0 ? $property_id : 0;
if ($paymentPropertyId <= 0) {
$error_msg = 'Please select a property.';
throw new Exception($error_msg);
}
$accountId = 0;
$accountMeta = [];
try {
$hasAcc = false;
try { $hasAcc = $pdo->query("SHOW TABLES LIKE 'accounts'")->rowCount() > 0; } catch (Throwable $eTbl) { $hasAcc = false; }
if ($hasAcc) {
$sqlA = "SELECT id, account_name, bank_name, account_number FROM accounts WHERE property_id = ? AND account_type = ? AND is_active = 1";
$paramsA = [$paymentPropertyId, $payment_type];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('accounts','company_id')) { $sqlA .= " AND (company_id = ? OR company_id IS NULL)"; $paramsA[] = $companyId; }
$sqlA .= " ORDER BY id DESC LIMIT 1";
$stA = $pdo->prepare($sqlA);
$stA->execute($paramsA);
$rowA = $stA->fetch(PDO::FETCH_ASSOC);
if ($rowA) {
$accountId = (int)($rowA['id'] ?? 0);
$accountMeta = [
'account_name' => (string)($rowA['account_name'] ?? ''),
'bank_name' => (string)($rowA['bank_name'] ?? ''),
'account_number' => (string)($rowA['account_number'] ?? ''),
];
}
}
} catch (Throwable $eAcc) {}
if ($accountId <= 0) {
$error_msg = 'No active bank account configured for this property and payment type (' . $payment_type . '). Please open Finance → Property Bank Accounts and ensure the ' . ucfirst($payment_type) . ' account is saved and Active.';
throw new Exception($error_msg);
}
$meta['account_id'] = $accountId;
$meta['account'] = $accountMeta;
if ($payment_type !== 'land') {
$chargeTotal = 0.0;
try {
$hasPc = $pdo->query("SHOW TABLES LIKE 'property_charges'")->rowCount() > 0;
if ($hasPc) {
$q = "SELECT charge_name, amount FROM property_charges WHERE property_id = ? AND is_active = 1";
$paramsPc = [$paymentPropertyId];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('property_charges','company_id')) { $q .= " AND (company_id = ? OR company_id IS NULL OR company_id = 0)"; $paramsPc[] = $companyId; }
$stPc = $pdo->prepare($q);
$stPc->execute($paramsPc);
$rowsPc = $stPc->fetchAll(PDO::FETCH_ASSOC) ?: [];
foreach ($rowsPc as $r) {
$nm = strtolower(trim((string)($r['charge_name'] ?? '')));
$amt = (float)($r['amount'] ?? 0);
if (!is_finite($amt) || $amt < 0) $amt = 0.0;
if ($payment_type === 'infrastructure' && (strpos($nm, 'infra') !== false || strpos($nm, 'infrastructure') !== false)) { $chargeTotal += $amt; }
if ($payment_type === 'excavation' && (strpos($nm, 'excav') !== false || strpos($nm, 'excavation') !== false || strpos($nm, 'escav') !== false)) { $chargeTotal += $amt; }
}
}
} catch (Throwable $ePc) {}
if ($chargeTotal <= 0) {
$error_msg = 'No ' . ucfirst($payment_type) . ' charge configured for this property. Please open Finance → Property Bank Accounts, select this property, and add an Active charge named "' . ucfirst($payment_type) . ' Fee" with the correct amount.';
throw new Exception($error_msg);
}
$finalStatuses = function_exists('kpiPaymentFinalizedStatuses') ? (array)kpiPaymentFinalizedStatuses() : ['verified','approved','paid','completed','success'];
$statusSql = function_exists('kpiSqlList') ? kpiSqlList($finalStatuses) : "('verified','approved','paid','completed','success')";
$payCompany = ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) ? " AND (company_id = " . (int)$companyId . " OR company_id IS NULL)" : "";
$propClause = (function_exists('tableHasColumn') && tableHasColumn('payments','property_id')) ? " AND property_id = " . (int)$paymentPropertyId : "";
$typeClause = (function_exists('tableHasColumn') && tableHasColumn('payments','payment_type')) ? " AND LOWER(TRIM(payment_type)) = " . $pdo->quote($payment_type) : "";
$userWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','user_id')) ? "user_id = " . (int)$clientId : "1=0";
$clientWhere = (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) ? "client_id = " . (int)$clientId : "1=0";
$paidApproved = 0.0;
try {
$paidApproved = (float)$pdo->query("SELECT COALESCE(SUM(amount),0) FROM payments WHERE (" . $userWhere . " OR " . $clientWhere . ") AND status IN {$statusSql}{$payCompany}{$propClause}{$typeClause}")->fetchColumn();
} catch (Throwable $ePaid) { $paidApproved = 0.0; }
$outstanding = max(0.0, $chargeTotal - $paidApproved);
if ($paid_now > ($outstanding + 0.01) && $outstanding > 0) {
$error_msg = ucfirst($payment_type) . ' payment exceeds outstanding balance. Outstanding: ₦' . number_format($outstanding, 2);
throw new Exception($error_msg);
}
$meta['charge_total'] = $chargeTotal;
$meta['charge_paid'] = $paidApproved;
$meta['charge_balance'] = $outstanding;
}
$crmClientId = 0;
try {
$hasClientsTbl = $pdo->query("SHOW TABLES LIKE 'clients'")->rowCount() > 0;
if ($hasClientsTbl) {
$uEmail = $client_email;
$uName = $client_name;
$uPhone = '';
if ($clientId > 0) {
try {
$sel = [];
if (function_exists('tableHasColumn') && tableHasColumn('users','email')) { $sel[] = 'email'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','name')) { $sel[] = 'name'; }
if (function_exists('tableHasColumn') && tableHasColumn('users','phone')) { $sel[] = 'phone'; }
if (!empty($sel)) {
$stU = $pdo->prepare("SELECT " . implode(',', $sel) . " FROM users WHERE id = ? LIMIT 1");
$stU->execute([$clientId]);
$ru = $stU->fetch(PDO::FETCH_ASSOC) ?: [];
if ($uEmail === '' && isset($ru['email'])) { $uEmail = trim((string)$ru['email']); }
if ($uName === '' && isset($ru['name'])) { $uName = trim((string)$ru['name']); }
if (isset($ru['phone'])) { $uPhone = trim((string)$ru['phone']); }
}
} catch (Throwable $eU) {}
}
try {
if (function_exists('tableHasColumn') && tableHasColumn('clients','user_id') && $clientId > 0) {
$q = $pdo->prepare("SELECT id FROM clients WHERE user_id = ? ORDER BY id DESC LIMIT 1");
$q->execute([$clientId]);
$crmClientId = (int)($q->fetchColumn() ?: 0);
}
} catch (Throwable $eC1) {}
if ($crmClientId <= 0 && $uEmail !== '' && function_exists('tableHasColumn') && tableHasColumn('clients','email')) {
try {
$q = $pdo->prepare("SELECT id FROM clients WHERE email = ? ORDER BY id DESC LIMIT 1");
$q->execute([$uEmail]);
$crmClientId = (int)($q->fetchColumn() ?: 0);
} catch (Throwable $eC2) {}
}
if ($crmClientId <= 0) {
try {
$cCols = []; $cPh = []; $cVals = [];
if (function_exists('tableHasColumn') && tableHasColumn('clients','user_id')) { $cCols[]='user_id'; $cPh[]='?'; $cVals[]=$clientId ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('clients','email')) {
$em = $uEmail !== '' ? $uEmail : ('client' . ($clientId ?: time()) . '+' . $uniqRef . '@local.invalid');
$cCols[]='email'; $cPh[]='?'; $cVals[]=$em;
}
if (function_exists('tableHasColumn') && tableHasColumn('clients','phone')) {
$ph = $uPhone !== '' ? $uPhone : '0000000000';
$cCols[]='phone'; $cPh[]='?'; $cVals[]=$ph;
}
if (function_exists('tableHasColumn') && tableHasColumn('clients','agent_id') && $userId > 0) { $cCols[]='agent_id'; $cPh[]='?'; $cVals[]=$userId; }
if (function_exists('tableHasColumn') && tableHasColumn('clients','status')) { $cCols[]='status'; $cPh[]='?'; $cVals[]='active'; }
if (function_exists('tableHasColumn') && tableHasColumn('clients','company_id') && $companyId) { $cCols[]='company_id'; $cPh[]='?'; $cVals[]=$companyId; }
if (function_exists('tableHasColumn') && tableHasColumn('clients','first_name')) {
$first = $uName !== '' ? preg_split('/\s+/', trim($uName))[0] : 'Client';
$cCols[]='first_name'; $cPh[]='?'; $cVals[]=$first;
}
if (function_exists('tableHasColumn') && tableHasColumn('clients','last_name')) {
$parts = $uName !== '' ? preg_split('/\s+/', trim($uName)) : [];
$last = count($parts) > 1 ? trim(implode(' ', array_slice($parts, 1))) : 'Client';
$cCols[]='last_name'; $cPh[]='?'; $cVals[]=$last;
}
if (function_exists('tableHasColumn') && tableHasColumn('clients','name') && $uName !== '') { $cCols[]='name'; $cPh[]='?'; $cVals[]=$uName; }
if (function_exists('tableHasColumn') && tableHasColumn('clients','created_at')) { $cCols[]='created_at'; $cPh[]='NOW()'; }
if (!empty($cCols)) {
$sqlC = "INSERT INTO clients (" . implode(',', $cCols) . ") VALUES (" . implode(',', $cPh) . ")";
$insC = $pdo->prepare($sqlC);
$insC->execute($cVals);
$crmClientId = (int)$pdo->lastInsertId();
}
} catch (Throwable $eInsC) { $crmClientId = 0; }
}
}
} catch (Throwable $eClients) { $crmClientId = 0; }
$dealId = (int)$existing_deal_id;
if ($dealId > 0) {
$existsInDeals = 0;
try {
$chk = $pdo->prepare("SELECT id FROM deals WHERE id = ? LIMIT 1");
$chk->execute([$dealId]);
$existsInDeals = (int)($chk->fetchColumn() ?: 0);
} catch (Throwable $eChkDeal) { $existsInDeals = 0; }
if ($existsInDeals <= 0) {
$resolved = 0;
try {
$hasDsTbl = $pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0;
if ($hasDsTbl && function_exists('tableHasColumn') && tableHasColumn('deals_submit','deal_id')) {
$st = $pdo->prepare("SELECT deal_id FROM deals_submit WHERE id = ? LIMIT 1");
$st->execute([$dealId]);
$resolved = (int)($st->fetchColumn() ?: 0);
}
} catch (Throwable $eResolve) { $resolved = 0; }
if ($resolved > 0) {
$dealId = $resolved;
} else {
try {
$pdo->exec("INSERT INTO deals (id) VALUES (NULL)");
$newDealId = (int)$pdo->lastInsertId();
if ($newDealId > 0) {
try {
$sets = [];
$valsSet = [];
if (function_exists('tableHasColumn') && tableHasColumn('deals','user_id') && $clientId > 0) { $sets[] = "user_id = ?"; $valsSet[] = $clientId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','property_id') && $paymentPropertyId > 0) { $sets[] = "property_id = ?"; $valsSet[] = $paymentPropertyId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','project_desc') && $project_desc !== '') { $sets[] = "project_desc = ?"; $valsSet[] = $project_desc; }
if (!empty($sets)) {
$valsSet[] = $newDealId;
$pdo->prepare("UPDATE deals SET " . implode(', ', $sets) . " WHERE id = ?")->execute($valsSet);
}
} catch (Throwable $eBackfill) {}
try {
$hasDsTbl2 = $pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0;
if ($hasDsTbl2 && function_exists('tableHasColumn') && tableHasColumn('deals_submit','deal_id')) {
$pdo->prepare("UPDATE deals_submit SET deal_id = ? WHERE id = ?")->execute([$newDealId, (int)$existing_deal_id]);
}
} catch (Throwable $eLinkNew) {}
$dealId = $newDealId;
}
} catch (Throwable $eCreateDeal) {}
}
}
if ($dealId <= 0) {
throw new Exception('Payment must be linked to an existing deal.');
}
} else {
try {
if (function_exists('tableHasColumn')) {
$defs = [
'user_id INT NULL',
'deal_id INT NULL',
'marketer_id INT NULL',
'marketer_name VARCHAR(255) NULL',
'deal_source VARCHAR(100) NULL',
'project_name VARCHAR(255) NULL',
'project_desc VARCHAR(255) NULL',
'project_id INT NULL',
'payment_plan VARCHAR(50) NULL',
'installment_start_date DATE NULL',
'policy_accepted TINYINT(1) DEFAULT 0',
'amount_offered DECIMAL(15,2) NULL',
'amount_paid_so_far DECIMAL(15,2) NULL',
'discount_amount DECIMAL(15,2) NULL',
'discount_approved_by VARCHAR(255) NULL',
'commission_percent DECIMAL(5,2) NULL',
'marketer_commission DECIMAL(15,2) NULL',
'agent_commission DECIMAL(15,2) NULL',
'balance_remaining DECIMAL(15,2) NULL',
'marketer_bank JSON NULL',
'agent_bank JSON NULL',
'notes TEXT NULL',
'receipt_file VARCHAR(255) NULL',
'status VARCHAR(50) NULL',
'submitted_by INT NULL',
'submitted_by_user INT NULL',
'submitted_by_role VARCHAR(50) NULL',
'company_id INT NULL',
'meta_json JSON NULL',
'created_at DATETIME DEFAULT CURRENT_TIMESTAMP'
];
foreach ($defs as $def) {
$col = trim(strtok($def, ' '));
try { $pdo->exec("ALTER TABLE deals_submit ADD COLUMN IF NOT EXISTS $def"); } catch (Throwable $eAdd) {
if (!tableHasColumn('deals_submit', $col)) {
try { $pdo->exec("ALTER TABLE deals_submit ADD COLUMN $def"); } catch (Throwable $eAdd2) {}
}
}
}
}
} catch (Throwable $eSchema) {}
$resolvedPropertyId = (int)($property_id ?? 0);
try {
if ($resolvedPropertyId <= 0) {
$hasProps = $pdo->query("SHOW TABLES LIKE 'properties'")->rowCount() > 0;
if ($hasProps && function_exists('tableHasColumn') && tableHasColumn('properties', 'title')) {
$cand = trim((string)($project_desc ?? ''));
if ($cand === '') { $cand = trim((string)($project_name ?? '')); }
if ($cand !== '') {
$stP1 = $pdo->prepare("SELECT id FROM properties WHERE LOWER(title) = LOWER(?) ORDER BY id DESC LIMIT 1");
$stP1->execute([$cand]);
$resolvedPropertyId = (int)($stP1->fetchColumn() ?: 0);
if ($resolvedPropertyId <= 0) {
$stP2 = $pdo->prepare("SELECT id FROM properties WHERE LOWER(title) LIKE ? ORDER BY id DESC LIMIT 1");
$stP2->execute(['%' . strtolower($cand) . '%']);
$resolvedPropertyId = (int)($stP2->fetchColumn() ?: 0);
}
}
}
}
} catch (Throwable $ePropResolve) { $resolvedPropertyId = 0; }
try {
$dCols = []; $dPh = []; $dVals = [];
// ALWAYS write core fields into deals (columns ensured at startup)
$dCols[]='user_id'; $dPh[]='?'; $dVals[]=$clientId ?: null;
// Attempt to resolve marketer_id from marketer_name if users table is present
$marketerId = 0;
try {
if ($marketer_name !== '' && function_exists('tableHasColumn') && tableHasColumn('users','name')) {
$qm = $pdo->prepare("SELECT id FROM users WHERE name = ? LIMIT 1");
$qm->execute([$marketer_name]);
$marketerId = (int)($qm->fetchColumn() ?: 0);
}
} catch (Throwable $eMk) {}
$dCols[]='marketer_id'; $dPh[]='?'; $dVals[]=$marketerId ?: null;
$dCols[]='marketer_name'; $dPh[]='?'; $dVals[]=$marketer_name ?: null;
$dCols[]='deal_source'; $dPh[]='?'; $dVals[]=$deal_source ?: null;
$resolvedProjectId = 0;
try {
if ($project_name !== '' && $pdo->query("SHOW TABLES LIKE 'projects'")->rowCount() > 0) {
$q = "SELECT id FROM projects WHERE (name = ? OR name LIKE ? OR ? LIKE CONCAT('%', name, '%'))";
$params = [$project_name, '%' . $project_name . '%', $project_name];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('projects','company_id')) {
$q .= " AND (company_id = ? OR company_id IS NULL)";
$params[] = $companyId;
}
$q .= " ORDER BY (name = ?) DESC, CHAR_LENGTH(name) DESC LIMIT 1";
$params[] = $project_name;
$stP = $pdo->prepare($q);
$stP->execute($params);
$resolvedProjectId = (int)($stP->fetchColumn() ?: 0);
}
} catch (Throwable $eProj) { $resolvedProjectId = 0; }
if (isset($meta) && is_array($meta)) { $meta['project_id'] = $resolvedProjectId ?: null; }
// Always store project_name (canonical) and project_desc for reference
$dCols[]='project_name'; $dPh[]='?'; $dVals[]=($project_name !== '' ? $project_name : ($project_desc ?: null));
$dCols[]='project_desc'; $dPh[]='?'; $dVals[]=$project_desc ?: null;
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','project_id')) { $dCols[]='project_id'; $dPh[]='?'; $dVals[]=$resolvedProjectId ?: null; }
$dCols[]='payment_plan'; $dPh[]='?'; $dVals[]=$plan_type ?: null;
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','installment_start_date')) { $dCols[]='installment_start_date'; $dPh[]='?'; $dVals[]=$installment_start_date ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','policy_accepted')) { $dCols[]='policy_accepted'; $dPh[]='?'; $dVals[]=$policy_accepted; }
$dCols[]='amount_offered'; $dPh[]='?'; $dVals[]=$amount_offered;
$dCols[]='amount_paid_so_far'; $dPh[]='?'; $dVals[]=$amount_paid_so_far;
$dCols[]='discount_amount'; $dPh[]='?'; $dVals[]=$discount_amount;
$dCols[]='discount_approved_by'; $dPh[]='?'; $dVals[]=$discount_approved_by ?: null;
$dCols[]='commission_percent'; $dPh[]='?'; $dVals[]=$commission_pct;
$dCols[]='marketer_commission'; $dPh[]='?'; $dVals[]=$marketer_comm;
$dCols[]='agent_commission'; $dPh[]='?'; $dVals[]=$agent_comm;
$dCols[]='balance_remaining'; $dPh[]='?'; $dVals[]=$balance_remaining;
$dCols[]='notes'; $dPh[]='?'; $dVals[]=$client_payment_status ?: null;
$dCols[]='receipt_file'; $dPh[]='?'; $dVals[]=$receipt ?: null;
$dCols[]='status'; $dPh[]='?'; $dVals[]='pending_verification';
$dCols[]='submitted_by'; $dPh[]='?'; $dVals[]=$performedByUserId > 0 ? (int)$performedByUserId : null;
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) { $dCols[]='submitted_by_user'; $dPh[]='?'; $dVals[]=(int)$performedByUserId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_role')) { $dCols[]='submitted_by_role'; $dPh[]='?'; $dVals[]=$performedByRole !== '' ? $performedByRole : null; }
if ($companyId) { $dCols[]='company_id'; $dPh[]='?'; $dVals[]=$companyId; }
$dCols[]='created_at'; $dPh[]='NOW()';
if (function_exists('tableHasColumn') ? tableHasColumn('deals_submit','marketer_bank') : true) {
$dCols[]='marketer_bank'; $dPh[]='?';
$dVals[] = !empty($meta['marketer_bank']) && is_array($meta['marketer_bank']) ? json_encode($meta['marketer_bank']) : null;
}
if (function_exists('tableHasColumn') ? tableHasColumn('deals_submit','agent_bank') : true) {
$dCols[]='agent_bank'; $dPh[]='?';
$dVals[] = !empty($meta['agent_bank']) && is_array($meta['agent_bank']) ? json_encode($meta['agent_bank']) : null;
}
if (function_exists('tableHasColumn') ? tableHasColumn('deals_submit','meta_json') : true) { $dCols[]='meta_json'; $dPh[]='?'; $dVals[] = json_encode($meta); }
if (!empty($dCols)) {
$sqlD = "INSERT INTO deals_submit (" . implode(',', $dCols) . ") VALUES (" . implode(',', $dPh) . ")";
try { @file_put_contents(__DIR__ . '/debug_sql.txt', "DEAL_SQL:\n".$sqlD."\nPARAMS:\n".print_r($dVals, true)."\n", FILE_APPEND); } catch (Throwable $eDbg) {}
try {
$insD = $pdo->prepare($sqlD);
$insD->execute($dVals);
$submitId = (int)$pdo->lastInsertId();
} catch (Throwable $eInsD) {
try { @file_put_contents(__DIR__ . '/debug_sql.txt', "DEAL_SQL_ERROR:\n".(string)$eInsD."\n", FILE_APPEND); } catch (Throwable $eDbgE) {}
$submitId = 0;
}
if (!empty($_GET['break_deal']) || !empty($_POST['break_deal'])) {
echo "SUBMIT ID: ".$submitId;
exit;
}
if ($submitId > 0) {
$dealId = $submitId;
try {
$cols2 = []; $ph2 = []; $vals2 = [];
if (function_exists('tableHasColumn') && tableHasColumn('deals','user_id')) { $cols2[]='user_id'; $ph2[]='?'; $vals2[]=$clientId ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) {
// reuse marketerId if available
$cols2[]='marketer_id'; $ph2[]='?'; $vals2[]=$marketerId ?: null;
}
if (function_exists('tableHasColumn') && tableHasColumn('deals','marketer_name')) { $cols2[]='marketer_name'; $ph2[]='?'; $vals2[]=$marketer_name ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','deal_source')) { $cols2[]='deal_source'; $ph2[]='?'; $vals2[]=$deal_source ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','project_name')) { $cols2[]='project_name'; $ph2[]='?'; $vals2[]=($project_name !== '' ? $project_name : ($project_desc ?: null)); }
if (function_exists('tableHasColumn') && tableHasColumn('deals','project_desc')) { $cols2[]='project_desc'; $ph2[]='?'; $vals2[]=$project_desc ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','project_id')) { $cols2[]='project_id'; $ph2[]='?'; $vals2[]=$resolvedProjectId ?: null; }
// Try to satisfy legacy 'deals' table requirements
if (function_exists('tableHasColumn') && tableHasColumn('deals','property_id') && $resolvedPropertyId > 0) { $cols2[]='property_id'; $ph2[]='?'; $vals2[]=$resolvedPropertyId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','client_id') && $crmClientId > 0) { $cols2[]='client_id'; $ph2[]='?'; $vals2[]=$crmClientId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','agent_id') && $userId > 0) { $cols2[]='agent_id'; $ph2[]='?'; $vals2[]=$userId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','type')) { $cols2[]='type'; $ph2[]='?'; $vals2[]='Sale'; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','value')) { $cols2[]='value'; $ph2[]='?'; $vals2[]=$amount_offered ?: $paid_now; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','date')) { $cols2[]='date'; $ph2[]='?'; $vals2[]=date('Y-m-d'); }
if (function_exists('tableHasColumn') && tableHasColumn('deals','reference')) { $cols2[]='reference'; $ph2[]='?'; $vals2[]='DEAL-' . $uniqRef; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','payment_plan')) { $cols2[]='payment_plan'; $ph2[]='?'; $vals2[]=$plan_type ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','amount_offered')) { $cols2[]='amount_offered'; $ph2[]='?'; $vals2[]=$amount_offered; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','amount_paid_so_far')) { $cols2[]='amount_paid_so_far'; $ph2[]='?'; $vals2[]=$amount_paid_so_far; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','discount_amount')) { $cols2[]='discount_amount'; $ph2[]='?'; $vals2[]=$discount_amount; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','discount_approved_by')) { $cols2[]='discount_approved_by'; $ph2[]='?'; $vals2[]=$discount_approved_by ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','commission_percent')) { $cols2[]='commission_percent'; $ph2[]='?'; $vals2[]=$commission_pct; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','marketer_commission')) { $cols2[]='marketer_commission'; $ph2[]='?'; $vals2[]=$marketer_comm; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','agent_commission')) { $cols2[]='agent_commission'; $ph2[]='?'; $vals2[]=$agent_comm; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','balance_remaining')) { $cols2[]='balance_remaining'; $ph2[]='?'; $vals2[]=$balance_remaining; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','notes')) { $cols2[]='notes'; $ph2[]='?'; $vals2[]=$client_payment_status ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','receipt_file')) { $cols2[]='receipt_file'; $ph2[]='?'; $vals2[]=$receipt ?: null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','status')) { $cols2[]='status'; $ph2[]='?'; $vals2[]='pending_verification'; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','submitted_by')) { $cols2[]='submitted_by'; $ph2[]='?'; $vals2[]=$performedByUserId > 0 ? (int)$performedByUserId : null; }
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals','company_id')) { $cols2[]='company_id'; $ph2[]='?'; $vals2[]=$companyId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','meta_json')) { $cols2[]='meta_json'; $ph2[]='?'; $vals2[] = json_encode($meta); }
if (function_exists('tableHasColumn') && tableHasColumn('deals','created_at')) { $cols2[]='created_at'; $ph2[]='NOW()'; }
if (!empty($cols2)) {
$sql2 = "INSERT INTO deals (" . implode(',', $cols2) . ") VALUES (" . implode(',', $ph2) . ")";
$ins2 = $pdo->prepare($sql2);
$bindVals2 = $vals2;
$ins2->execute($bindVals2);
$dealRowId = (int)$pdo->lastInsertId();
if ($dealRowId > 0) {
$dealId = $dealRowId;
try {
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','deal_id')) {
$pdo->prepare("UPDATE deals_submit SET deal_id = ? WHERE id = ?")->execute([$dealRowId, $submitId]);
}
} catch (Throwable $eLinkSub) {}
}
} else {
try {
$pdo->exec("INSERT INTO deals (id) VALUES (NULL)");
$dealRowId = (int)$pdo->lastInsertId();
if ($dealRowId > 0) { $dealId = $dealRowId; }
} catch (Throwable $eOnlyId) {}
}
} catch (Throwable $eDealShadow) {}
}
}
} catch (Throwable $de) {}
}
$fields = []; $places = []; $vals = [];
if ($clientId > 0 && tableHasColumn('payments','user_id')) { $fields[]='user_id'; $places[]='?'; $vals[]=$clientId; }
if ($clientId > 0 && tableHasColumn('payments','client_id')) { $fields[]='client_id'; $places[]='?'; $vals[]=$clientId; }
if ($paymentPropertyId > 0 && tableHasColumn('payments','property_id')) { $fields[]='property_id'; $places[]='?'; $vals[]=$paymentPropertyId; }
if (tableHasColumn('payments','payment_type')) { $fields[]='payment_type'; $places[]='?'; $vals[]=$payment_type; }
if ($accountId > 0 && tableHasColumn('payments','account_id')) { $fields[]='account_id'; $places[]='?'; $vals[]=$accountId; }
if ($performedByUserId > 0 && tableHasColumn('payments','submitted_by_user')) { $fields[]='submitted_by_user'; $places[]='?'; $vals[]=(int)$performedByUserId; }
if (tableHasColumn('payments','submitted_by_name')) { $fields[]='submitted_by_name'; $places[]='?'; $vals[]=$performedByName !== '' ? $performedByName : null; }
if (tableHasColumn('payments','submitted_by_role')) { $fields[]='submitted_by_role'; $places[]='?'; $vals[]=$performedByRole !== '' ? $performedByRole : null; }
$validDealId = 0;
if ($dealId > 0) {
try { $chk = $pdo->prepare("SELECT id FROM deals WHERE id = ? LIMIT 1"); $chk->execute([$dealId]); $validDealId = (int)($chk->fetchColumn() ?: 0); } catch (Throwable $e) {}
}
if ($validDealId <= 0) {
throw new Exception('Payment must be linked to a valid deal.');
}
if ($validDealId > 0 && tableHasColumn('payments','deal_id')) { $fields[]='deal_id'; $places[]='?'; $vals[]=$validDealId; }
if (tableHasColumn('payments','amount')) { $fields[]='amount'; $places[]='?'; $vals[]=$paid_now; }
if (tableHasColumn('payments','status')) { $fields[]='status'; $places[]='?'; $vals[]=$isDuplicate ? 'duplicate' : 'pending_verification'; }
if (tableHasColumn('payments','payment_method')) { $fields[]='payment_method'; $places[]='?'; $vals[]='Bank Transfer'; }
elseif (tableHasColumn('payments','method')) { $fields[]='method'; $places[]='?'; $vals[]='Bank Transfer'; }
if (tableHasColumn('payments','reference')) {
$fields[]='reference'; $places[]='?';
$vals[]='deal-submission-' . $uniqRef;
}
if (tableHasColumn('payments','proof_file')) { $fields[]='proof_file'; $places[]='?'; $vals[]=$receipt ?: null; }
if ($receipt_hash && tableHasColumn('payments','receipt_hash')) { $fields[]='receipt_hash'; $places[]='?'; $vals[]=$receipt_hash; }
if ($sqm > 0 && tableHasColumn('payments','sqm')) { $fields[]='sqm'; $places[]='?'; $vals[]=$sqm; }
if ($companyId && tableHasColumn('payments','company_id')) { $fields[]='company_id'; $places[]='?'; $vals[]=$companyId; }
if (tableHasColumn('payments','created_at')) { $fields[]='created_at'; $places[]='NOW()'; }
if ($fields) {
$sql = "INSERT INTO payments (" . implode(',', $fields) . ") VALUES (" . implode(',', $places) . ")";
try { @file_put_contents(__DIR__ . '/debug_sql.txt', "PAYMENT_SQL:\n".$sql."\nPARAMS:\n".print_r($vals, true)."\n", FILE_APPEND); } catch (Throwable $eDbg2) {}
$ins = $pdo->prepare($sql);
$ins->execute($vals);
$pid = (int)$pdo->lastInsertId();
if ($validDealId > 0) {
try {
$hasDealIdCol = true;
if (function_exists('tableHasColumn')) {
$hasDealIdCol = tableHasColumn('payments','deal_id');
}
if (!$hasDealIdCol) {
try { $pdo->exec("ALTER TABLE payments ADD COLUMN deal_id INT NULL"); } catch (Throwable $eAlt) {}
}
$pdo->prepare("UPDATE payments SET deal_id = ? WHERE id = ?")->execute([$validDealId, $pid]);
try { @file_put_contents(__DIR__ . '/debug_sql.txt', "PAYMENT_LINK:\nUPDATE payments SET deal_id = {$validDealId} WHERE id = {$pid}\n", FILE_APPEND); } catch (Throwable $eDbg3) {}
} catch (Throwable $eLink) {
try { @file_put_contents(__DIR__ . '/debug_sql.txt', "PAYMENT_LINK_ERROR:\n".(string)$eLink."\n", FILE_APPEND); } catch (Throwable $eDbg4) {}
}
}
// Optional debug capture when requested
if (!empty($_GET['debug']) || !empty($_POST['debug'])) {
echo "<pre style=\"white-space:pre-wrap\">";
echo "deal_data:\n"; print_r(['id'=>$dealId,'cols'=>$dCols ?? [],'vals'=>$dVals ?? []]);
echo "\npayment_data:\n"; print_r(['id'=>$pid,'fields'=>$fields,'vals'=>$vals]);
echo "</pre>";
}
if (tableHasColumn('payments','meta_json')) {
$up = $pdo->prepare("UPDATE payments SET meta_json = ? WHERE id = ?");
$up->execute([json_encode($meta), $pid]);
} else {
if (function_exists('logActivity')) {
logActivity($userId, 'Deal Submission', 'Payment ID '.$pid.' | '.json_encode($meta));
}
}
try {
$hasTx = $pdo->query("SHOW TABLES LIKE 'transactions'")->rowCount() > 0;
if ($hasTx) {
$tCols = []; $tPh = []; $tVals = [];
if ($clientId > 0 && tableHasColumn('transactions','user_id')) { $tCols[]='user_id'; $tPh[]='?'; $tVals[]=$clientId; }
if ($dealId > 0 && tableHasColumn('transactions','deal_id')) { $tCols[]='deal_id'; $tPh[]='?'; $tVals[]=$dealId; }
if (tableHasColumn('transactions','amount')) { $tCols[]='amount'; $tPh[]='?'; $tVals[]=$paid_now; }
if (tableHasColumn('transactions','transaction_type')) { $tCols[]='transaction_type'; $tPh[]='?'; $tVals[]='deal_submission'; }
if (tableHasColumn('transactions','status')) { $tCols[]='status'; $tPh[]='?'; $tVals[]=$isDuplicate ? 'duplicate' : 'pending_verification'; }
if (tableHasColumn('transactions','reference')) { $tCols[]='reference'; $tPh[]='?'; $tVals[]=('deal-submission-' . $uniqRef); }
elseif (tableHasColumn('transactions','ref')) { $tCols[]='ref'; $tPh[]='?'; $tVals[]=('deal-submission-' . $uniqRef); }
if ($companyId && tableHasColumn('transactions','company_id')) { $tCols[]='company_id'; $tPh[]='?'; $tVals[]=$companyId; }
if (tableHasColumn('transactions','created_at')) { $tCols[]='created_at'; $tPh[]='NOW()'; }
if (!empty($tCols)) {
$sqlT = "INSERT INTO transactions (" . implode(',', $tCols) . ") VALUES (" . implode(',', $tPh) . ")";
$insT = $pdo->prepare($sqlT); $insT->execute($tVals);
}
if (!empty($transactions) && tableHasColumn('transactions','amount')) {
foreach ($transactions as $tx) {
$txAmt = (float)($tx['amount'] ?? 0);
$txDate = (string)($tx['date'] ?? '');
if ($txAmt > 0 && $txDate !== '') {
$tCols2 = []; $tPh2 = []; $tVals2 = [];
if ($clientId > 0 && tableHasColumn('transactions','user_id')) { $tCols2[]='user_id'; $tPh2[]='?'; $tVals2[]=$clientId; }
if ($dealId > 0 && tableHasColumn('transactions','deal_id')) { $tCols2[]='deal_id'; $tPh2[]='?'; $tVals2[]=$dealId; }
if (tableHasColumn('transactions','amount')) { $tCols2[]='amount'; $tPh2[]='?'; $tVals2[]=$txAmt; }
if (tableHasColumn('transactions','transaction_type')) { $tCols2[]='transaction_type'; $tPh2[]='?'; $tVals2[]='installment'; }
if (tableHasColumn('transactions','status')) { $tCols2[]='status'; $tPh2[]='?'; $tVals2[]='pending_verification'; }
if (tableHasColumn('transactions','reference')) { $tCols2[]='reference'; $tPh2[]='?'; $tVals2[]=('deal-submission-' . $uniqRef); }
elseif (tableHasColumn('transactions','ref')) { $tCols2[]='ref'; $tPh2[]='?'; $tVals2[]=('deal-submission-' . $uniqRef); }
if ($companyId && tableHasColumn('transactions','company_id')) { $tCols2[]='company_id'; $tPh2[]='?'; $tVals2[]=$companyId; }
if (tableHasColumn('transactions','created_at')) { $tCols2[]='created_at'; $tPh2[]='?'; $tVals2[]=$txDate.' 00:00:00'; }
$sqlT2 = "INSERT INTO transactions (" . implode(',', $tCols2) . ") VALUES (" . implode(',', $tPh2) . ")";
$insT2 = $pdo->prepare($sqlT2); $insT2->execute($tVals2);
}
}
}
}
} catch (Throwable $eTx) {}
$success_msg = 'Submission recorded. Status: ' . ($isDuplicate ? 'Duplicate' : 'Pending Finance Verification') . '.';
if ($client_was_created && $client_email !== '') {
try {
$appUrlSetup = $setup_link;
if ($appUrlSetup === '') {
$appUrlSetup = function_exists('getSetting') ? (string)getSetting('app_url', '') : '';
if ($appUrlSetup === '') { $appUrlSetup = 'login.php'; }
}
$_SESSION['client_access_setup'] = [
'email' => $client_email,
'password' => $client_created_password,
'login_link' => $appUrlSetup,
'otp_code' => $setup_code,
'client_id' => (int)$clientId,
];
} catch (Throwable $eSess) {}
}
$sendEmails = (function_exists('getSetting') ? (getSetting('onboarding_send_emails','off') === 'on') : false);
$isWindowsOs = (stripos(PHP_OS, 'WIN') === 0);
if ($sendEmails && !$isWindowsOs) {
try {
$clientEmail = '';
$clientName = $client_name;
if ($clientId > 0 && function_exists('tableHasColumn') && tableHasColumn('users','email')) {
$se = $pdo->prepare("SELECT email, " . (tableHasColumn('users','name') ? "name" : "email") . " AS display_name FROM users WHERE id = ? LIMIT 1");
$se->execute([$clientId]);
$rowE = $se->fetch(PDO::FETCH_ASSOC);
if ($rowE) { $clientEmail = (string)($rowE['email'] ?? ''); $clientName = (string)($rowE['display_name'] ?? $client_name); }
}
$companyName = function_exists('getSetting') ? getSetting('company_name', 'Aiben Properties') : 'Aiben Properties';
$noreplyEmail = function_exists('getSetting') ? getSetting('noreply_email', 'no-reply@aibenproperties.local') : 'no-reply@aibenproperties.local';
$appUrl = function_exists('getSetting') ? getSetting('app_url', 'http://localhost/Aibenproperties/index.php') : 'http://localhost/Aibenproperties/index.php';
if ($clientEmail !== '') {
$subjectC = $client_was_created ? "Your Client Portal Login Details" : "Payment received - Pending Verification";
if ($client_was_created && $client_created_password !== '') {
$bodyC = "Dear {$clientName},\n\nYour client portal account has been created.\n\nLogin URL: {$appUrl}\nEmail: {$clientEmail}\nPassword: {$client_created_password}\n\nPlease login and change your password immediately.\n\nThank you,\n{$companyName}";
} else {
$bodyC = "Dear {$clientName},\n\nWe have recorded your payment submission and it is pending verification by Accounts.\n\nClient Dashboard: {$appUrl}\nLogin with your email.\n\nThank you,\n{$companyName}";
}
if (function_exists('sendEmail')) {
@sendEmail($clientEmail, $subjectC, $bodyC);
} else {
$headersC = "From: {$noreplyEmail}\r\n";
@mail($clientEmail, $subjectC, $bodyC, $headersC);
}
}
if ($userId > 0 && function_exists('tableHasColumn') && tableHasColumn('users','email')) {
$su = $pdo->prepare("SELECT email, " . (tableHasColumn('users','name') ? "name" : "email") . " AS display_name FROM users WHERE id = ? LIMIT 1");
$su->execute([$userId]);
$rowS = $su->fetch(PDO::FETCH_ASSOC);
if ($rowS && !empty($rowS['email'])) {
$subjectS = "Submission recorded for client";
$extra = '';
if ($client_was_created && $client_created_password !== '') {
$extra = "\n\nClient portal login created:\nEmail: {$clientEmail}\nPassword: {$client_created_password}\nLogin: {$appUrl}\n";
}
$bodyS = "Hello " . (string)($rowS['display_name'] ?? '') . ",\n\nA client payment submission has been recorded and is pending verification.\nClient: {$clientName}\nAmount: " . number_format($paid_now, 2) . "\nStatus: " . ($isDuplicate ? "Duplicate" : "Pending Finance Verification") . "{$extra}\n\nRegards,\n{$companyName}";
if (function_exists('sendEmail')) {
@sendEmail((string)$rowS['email'], $subjectS, $bodyS);
} else {
$headersS = "From: {$noreplyEmail}\r\n";
@mail((string)$rowS['email'], $subjectS, $bodyS, $headersS);
}
}
}
} catch (Throwable $eNot) {}
}
} else {
$error_msg = 'Unable to record submission with current schema.';
}
if (empty($error_msg) && !$isAjax) {
$_SESSION['deal_success'] = $success_msg;
header("Location: deal-submission.php?success=1");
exit;
}
} catch (Throwable $e) {
$error_msg = 'Submission failed: ' . (string)$e->getMessage();
}
}
if ($isAjax && $_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($error_msg) && empty($success_msg)) {
$error_msg = 'Submission failed. Please try again.';
}
try {
if (session_status() === PHP_SESSION_NONE) { @session_start(); }
if (empty($error_msg) && !empty($success_msg)) {
$_SESSION['deal_success'] = $success_msg;
}
} catch (Throwable $e) {}
try {
while (ob_get_level() > 0) { @ob_end_clean(); }
} catch (Throwable $e) {}
header('Content-Type: application/json; charset=utf-8');
$payload = [
'success' => empty($error_msg),
'message' => $success_msg,
'error' => $error_msg
];
$json = json_encode($payload);
if ($json === false) {
$json = json_encode(['success' => false, 'message' => '', 'error' => 'Server encoding error.']);
}
echo $json;
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['quick_deal'])) {
$q_client_id = (int)($_POST['q_client_id'] ?? 0);
$q_client = trim($_POST['q_client_name'] ?? '');
$q_property = trim($_POST['q_property'] ?? '');
$q_amount = (float)str_replace(',', '', $_POST['q_amount_paid'] ?? '0');
$q_notes = trim($_POST['q_payment_notes'] ?? '');
$q_receipt = safeUploadReceipt($_FILES['q_receipt_file'] ?? null);
$q_receipt_hash = '';
$qIsDuplicate = false;
if ($q_receipt && file_exists($q_receipt)) {
try {
$q_receipt_hash = sha1_file($q_receipt);
if (tableHasColumn('payments','receipt_hash')) {
$chk = $pdo->prepare("SELECT id FROM payments WHERE receipt_hash = ? AND status <> 'rejected' LIMIT 1");
$chk->execute([$q_receipt_hash]);
$qIsDuplicate = (bool)$chk->fetchColumn();
} elseif (tableHasColumn('payments','proof_file')) {
$chk2 = $pdo->prepare("SELECT id FROM payments WHERE proof_file = ? AND status <> 'rejected' LIMIT 1");
$chk2->execute([$q_receipt]);
$qIsDuplicate = (bool)$chk2->fetchColumn();
}
} catch (Throwable $e) {}
}
if ($q_client_id && $q_client === '') {
try {
$stmt = $pdo->prepare("SELECT name FROM users WHERE id = ? LIMIT 1");
$stmt->execute([$q_client_id]);
$q_client = (string)($stmt->fetchColumn() ?: '');
} catch (Throwable $e) {}
}
$meta = [
'client_id' => $q_client_id,
'client_name' => $q_client,
'property' => $q_property,
'notes' => $q_notes,
'submitted_by_role' => $role,
'submitted_by_user' => $userId,
'recorded_by_role' => $role,
'recorded_by_user' => $userId,
];
try {
$dealId = 0;
try {
$dCols = []; $dPh = []; $dVals = [];
$marketer_name_q = '';
$role_q = $role;
try {
if ($userId > 0 && function_exists('tableHasColumn') && tableHasColumn('users','name')) {
$qs = $pdo->prepare("SELECT name, role FROM users WHERE id = ? LIMIT 1");
$qs->execute([$userId]);
$rowU = $qs->fetch(PDO::FETCH_ASSOC);
$marketer_name_q = (string)($rowU['name'] ?? '');
$role_q = strtolower((string)($rowU['role'] ?? $role_q));
}
} catch (Throwable $eMkQ) {}
$performedNameQ = $marketer_name_q;
try {
if ($performedNameQ === '' && $userId > 0) {
$qs2 = $pdo->prepare("SELECT name, full_name, first_name, last_name FROM users WHERE id = ? LIMIT 1");
$qs2->execute([$userId]);
$u2 = $qs2->fetch(PDO::FETCH_ASSOC) ?: [];
$performedNameQ = trim((string)($u2['name'] ?? ''));
if ($performedNameQ === '') { $performedNameQ = trim((string)($u2['full_name'] ?? '')); }
if ($performedNameQ === '') { $performedNameQ = trim((string)((string)($u2['first_name'] ?? '') . ' ' . (string)($u2['last_name'] ?? ''))); }
}
} catch (Throwable $ePq) {}
if ($performedNameQ !== '') { $meta['submitted_by_name'] = $performedNameQ; $meta['recorded_by_name'] = $performedNameQ; }
$deal_source_q = ucfirst($role_q ?: 'Marketing');
$marketerId_q = 0;
try {
if ($marketer_name_q !== '' && function_exists('tableHasColumn') && tableHasColumn('users','name')) {
$qm = $pdo->prepare("SELECT id FROM users WHERE name = ? LIMIT 1");
$qm->execute([$marketer_name_q]);
$marketerId_q = (int)($qm->fetchColumn() ?: 0);
}
} catch (Throwable $eM2) {}
if (tableHasColumn('deals_submit','user_id')) { $dCols[]='user_id'; $dPh[]='?'; $dVals[]=$q_client_id ?: null; }
if (tableHasColumn('deals_submit','marketer_id')) { $dCols[]='marketer_id'; $dPh[]='?'; $dVals[]=$marketerId_q ?: null; }
if (tableHasColumn('deals_submit','marketer_name')) { $dCols[]='marketer_name'; $dPh[]='?'; $dVals[]=$marketer_name_q ?: null; }
if (tableHasColumn('deals_submit','deal_source')) { $dCols[]='deal_source'; $dPh[]='?'; $dVals[]=$deal_source_q ?: null; }
if (tableHasColumn('deals_submit','project_name')) { $dCols[]='project_name'; $dPh[]='?'; $dVals[]=$q_property ?: null; }
if (tableHasColumn('deals_submit','project_desc')) { $dCols[]='project_desc'; $dPh[]='?'; $dVals[]=$q_property ?: null; }
if (tableHasColumn('deals_submit','payment_plan')) { $dCols[]='payment_plan'; $dPh[]='?'; $dVals[]='installment'; }
if (tableHasColumn('deals_submit','amount_offered')) { $dCols[]='amount_offered'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','amount_paid_so_far')) { $dCols[]='amount_paid_so_far'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','discount_amount')) { $dCols[]='discount_amount'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','discount_approved_by')) { $dCols[]='discount_approved_by'; $dPh[]='?'; $dVals[]=null; }
if (tableHasColumn('deals_submit','commission_percent')) { $dCols[]='commission_percent'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','marketer_commission')) { $dCols[]='marketer_commission'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','agent_commission')) { $dCols[]='agent_commission'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','balance_remaining')) { $dCols[]='balance_remaining'; $dPh[]='?'; $dVals[]=0.00; }
if (tableHasColumn('deals_submit','notes')) { $dCols[]='notes'; $dPh[]='?'; $dVals[]=$q_notes ?: null; }
if (tableHasColumn('deals_submit','receipt_file')) { $dCols[]='receipt_file'; $dPh[]='?'; $dVals[]=$q_receipt ?: null; }
if (tableHasColumn('deals_submit','status')) { $dCols[]='status'; $dPh[]='?'; $dVals[]='pending_verification'; }
if (tableHasColumn('deals_submit','submitted_by')) { $dCols[]='submitted_by'; $dPh[]='?'; $dVals[]=$userId > 0 ? (int)$userId : null; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) { $dCols[]='submitted_by_user'; $dPh[]='?'; $dVals[]=(int)$userId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_role')) { $dCols[]='submitted_by_role'; $dPh[]='?'; $dVals[]=$role !== '' ? $role : null; }
if ($companyId && tableHasColumn('deals_submit','company_id')) { $dCols[]='company_id'; $dPh[]='?'; $dVals[]=$companyId; }
if (tableHasColumn('deals_submit','created_at')) { $dCols[]='created_at'; $dPh[]='NOW()'; }
if (tableHasColumn('deals_submit','marketer_bank')) { $dCols[]='marketer_bank'; $dPh[]='?'; $dVals[] = !empty($meta['marketer_bank']) && is_array($meta['marketer_bank']) ? json_encode($meta['marketer_bank']) : null; }
if (tableHasColumn('deals_submit','agent_bank')) { $dCols[]='agent_bank'; $dPh[]='?'; $dVals[] = !empty($meta['agent_bank']) && is_array($meta['agent_bank']) ? json_encode($meta['agent_bank']) : null; }
if (tableHasColumn('deals_submit','meta_json')) { $dCols[]='meta_json'; $dPh[]='?'; $dVals[] = json_encode($meta); }
if (!empty($dCols)) {
$sqlD = "INSERT INTO deals_submit (" . implode(',', $dCols) . ") VALUES (" . implode(',', $dPh) . ")";
$insD = $pdo->prepare($sqlD);
$insD->execute($dVals);
$submitId = (int)$pdo->lastInsertId();
}
} catch (Throwable $de) {}
$fields = []; $places = []; $vals = [];
if (tableHasColumn('payments','user_id')) { $fields[]='user_id'; $places[]='?'; $vals[]=$q_client_id ?: null; }
if ($dealId && tableHasColumn('payments','deal_id')) { $fields[]='deal_id'; $places[]='?'; $vals[]=$dealId; }
if (tableHasColumn('payments','amount')) { $fields[]='amount'; $places[]='?'; $vals[]=$q_amount; }
if (tableHasColumn('payments','status')) { $fields[]='status'; $places[]='?'; $vals[]=$qIsDuplicate ? 'duplicate' : 'pending_verification'; }
if ($userId > 0 && tableHasColumn('payments','submitted_by_user')) { $fields[]='submitted_by_user'; $places[]='?'; $vals[]=(int)$userId; }
if (tableHasColumn('payments','submitted_by_name')) { $fields[]='submitted_by_name'; $places[]='?'; $vals[]=!empty($performedNameQ) ? $performedNameQ : ($marketer_name_q ?: null); }
if (tableHasColumn('payments','submitted_by_role')) { $fields[]='submitted_by_role'; $places[]='?'; $vals[]=$role !== '' ? $role : null; }
if (tableHasColumn('payments','reference')) {
$fields[]='reference'; $places[]='?';
$uniq = date('YmdHis') . '-' . random_int(1000,9999);
$vals[]='deal-quick-' . $uniq;
}
if (tableHasColumn('payments','proof_file')) { $fields[]='proof_file'; $places[]='?'; $vals[]=$q_receipt ?: null; }
if ($q_receipt_hash && tableHasColumn('payments','receipt_hash')) { $fields[]='receipt_hash'; $places[]='?'; $vals[]=$q_receipt_hash; }
if ($companyId && tableHasColumn('payments','company_id')) { $fields[]='company_id'; $places[]='?'; $vals[]=$companyId; }
if (tableHasColumn('payments','created_at')) { $fields[]='created_at'; $places[]='NOW()'; }
if ($fields) {
$sql = "INSERT INTO payments (" . implode(',', $fields) . ") VALUES (" . implode(',', $places) . ")";
$ins = $pdo->prepare($sql);
$ins->execute($vals);
$pid = (int)$pdo->lastInsertId();
if (tableHasColumn('payments','meta_json')) {
$up = $pdo->prepare("UPDATE payments SET meta_json = ? WHERE id = ?");
$up->execute([json_encode($meta), $pid]);
}
try {
$hasTx = $pdo->query("SHOW TABLES LIKE 'transactions'")->rowCount() > 0;
if ($hasTx) {
$tCols = []; $tPh = []; $tVals = [];
if ($q_client_id > 0 && tableHasColumn('transactions','user_id')) { $tCols[]='user_id'; $tPh[]='?'; $tVals[]=$q_client_id; }
if ($dealId > 0 && tableHasColumn('transactions','deal_id')) { $tCols[]='deal_id'; $tPh[]='?'; $tVals[]=$dealId; }
if (tableHasColumn('transactions','amount')) { $tCols[]='amount'; $tPh[]='?'; $tVals[]=$q_amount; }
if (tableHasColumn('transactions','transaction_type')) { $tCols[]='transaction_type'; $tPh[]='?'; $tVals[]='deal_quick'; }
if (tableHasColumn('transactions','status')) { $tCols[]='status'; $tPh[]='?'; $tVals[]=$qIsDuplicate ? 'duplicate' : 'pending_verification'; }
if (tableHasColumn('transactions','reference')) { $tCols[]='reference'; $tPh[]='?'; $tVals[]=('deal-quick-' . date('YmdHis')); }
elseif (tableHasColumn('transactions','ref')) { $tCols[]='ref'; $tPh[]='?'; $tVals[]=('deal-quick-' . date('YmdHis')); }
if ($companyId && tableHasColumn('transactions','company_id')) { $tCols[]='company_id'; $tPh[]='?'; $tVals[]=$companyId; }
if (tableHasColumn('transactions','created_at')) { $tCols[]='created_at'; $tPh[]='NOW()'; }
if (!empty($tCols)) {
$sqlT = "INSERT INTO transactions (" . implode(',', $tCols) . ") VALUES (" . implode(',', $tPh) . ")";
$insT = $pdo->prepare($sqlT); $insT->execute($tVals);
}
if (!empty($meta['notes']) && tableHasColumn('transactions','notes')) {
$pdo->prepare("UPDATE transactions SET notes = ? WHERE " . (tableHasColumn('transactions','reference') ? "reference" : "ref") . " = ? ORDER BY id DESC LIMIT 1")->execute([(string)$meta['notes'], (string)($meta['payment_reference'] ?? '')]);
}
}
} catch (Throwable $eTx2) {}
$success_msg = 'Submission recorded. Status: ' . ($qIsDuplicate ? 'Duplicate' : 'Pending Finance Verification') . '.';
if (!$qIsDuplicate) {
try {
if (function_exists('ap_notify_roles')) {
$clientLabel = '';
try {
if ($q_client_id > 0 && function_exists('tableHasColumn') && tableHasColumn('users','email')) {
$nmCol = (function_exists('tableHasColumn') && tableHasColumn('users','name')) ? 'name' : (function_exists('tableHasColumn') && tableHasColumn('users','full_name') ? 'full_name' : null);
$sel = "email" . ($nmCol ? ", {$nmCol} AS nm" : "");
$stC = $pdo->prepare("SELECT {$sel} FROM users WHERE id = ? LIMIT 1");
$stC->execute([$q_client_id]);
$rC = $stC->fetch(PDO::FETCH_ASSOC) ?: [];
$em = trim((string)($rC['email'] ?? ''));
$nm = trim((string)($rC['nm'] ?? ''));
$clientLabel = trim($nm !== '' ? $nm : $em);
}
} catch (Throwable $eCl) { $clientLabel = ''; }
if ($clientLabel === '') { $clientLabel = 'Client #' . (int)$q_client_id; }
$amtLabel = function_exists('formatCurrency') ? formatCurrency((float)$q_amount) : ('₦' . number_format((float)$q_amount, 2));
$proj = trim((string)$q_property);
$msg = ($clientLabel !== '' ? ($clientLabel . ' ') : '') . 'submitted a deal payment of ' . $amtLabel . ($proj !== '' ? (' for ' . $proj) : '') . '.';
ap_notify_roles($pdo, ['finance_manager','finance','finance_officer','accountant','accounting'], [
'department' => 'Finance',
'title' => 'Deal submitted (Finance verification)',
'message' => $msg,
'type' => 'approval',
'priority' => 'approval',
'related_id' => (int)$pid,
'related_module' => 'finance-payments.php'
]);
}
} catch (Throwable $eNote) {}
}
if (empty($error_msg) && !$isAjax) {
$_SESSION['deal_success'] = $success_msg;
header("Location: deal-submission.php?success=1");
exit;
}
} else {
$error_msg = 'Unable to record submission.';
}
} catch (Throwable $e) {
$error_msg = 'Submission failed: ' . (string)$e->getMessage();
}
}
if ($isAjax && $_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
echo json_encode([
'success' => empty($error_msg),
'message' => $success_msg,
'error' => $error_msg
]);
exit;
}
if ($isAjax) {
$ok = ($error_msg === '' && $success_msg !== '');
$resp = ['success' => $ok, 'message' => $success_msg, 'error' => $error_msg];
if (function_exists('http_response_code')) { http_response_code($ok ? 200 : 400); }
@ob_end_clean();
header('Content-Type: application/json');
echo json_encode($resp);
exit;
}
try {
$clients = [];
$nameCol = (function_exists('tableHasColumn') && tableHasColumn('users','name')) ? 'name' : (function_exists('tableHasColumn') && tableHasColumn('users','username') ? 'username' : 'id');
$isSuper = in_array($role, ['super_admin','admin'], true);
$isFinance = in_array($role, ['finance','finance_officer','finance_manager','accountant','accounting'], true);
$isMarketing = in_array($role, ['marketing','sales','agent','sales_agent'], true);
$isContact = in_array($role, ['contact_rep','customer_rep','contact_centre','contact_center'], true);
if ($isSuper || $isFinance) {
$sql = "SELECT id, {$nameCol} AS name, email FROM users WHERE role = 'client'";
$params = [];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('users','company_id')) { $sql .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
$sql .= " ORDER BY {$nameCol} ASC";
$clq = $pdo->prepare($sql);
$clq->execute($params);
$clients = $clq->fetchAll(PDO::FETCH_ASSOC) ?: [];
} else {
$ids = [];
try {
if (function_exists('hasColumn') && hasColumn($pdo,'users','created_by')) {
$qU = "SELECT id FROM users WHERE role = 'client' AND created_by = ?";
$paramsU = [$userId];
if ($companyId && function_exists('hasColumn') && hasColumn($pdo,'users','company_id')) { $qU .= " AND (company_id = ? OR company_id IS NULL)"; $paramsU[] = $companyId; }
$stU = $pdo->prepare($qU);
$stU->execute($paramsU);
foreach ($stU->fetchAll(PDO::FETCH_COLUMN) ?: [] as $cid) {
$cid = (int)$cid;
if ($cid > 0) { $ids[$cid] = true; }
}
}
} catch (Throwable $eOwn) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'payments'")->rowCount() > 0) {
$pCols = [];
$pCols[] = "user_id";
if (function_exists('tableHasColumn') && tableHasColumn('payments','client_id')) { $pCols[] = "client_id"; }
$pWhere = [];
$pWhere[] = "(submitted_by_user = ?)";
$pParams = [$userId];
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('payments','company_id')) { $pWhere[] = "(company_id = ? OR company_id IS NULL)"; $pParams[] = $companyId; }
if ($isContact) { $pWhere[] = "(submitted_by_role IS NULL OR submitted_by_role = '' OR submitted_by_role = 'contact_centre' OR submitted_by_role = 'contact' OR submitted_by_role = 'customer_rep' OR submitted_by_role = 'contact_rep')"; }
$ps = $pdo->prepare("SELECT " . implode(',', $pCols) . " FROM payments WHERE " . implode(" AND ", $pWhere));
$ps->execute($pParams);
foreach ($ps->fetchAll(PDO::FETCH_ASSOC) ?: [] as $r) {
foreach (['user_id','client_id'] as $k) {
$cid = (int)($r[$k] ?? 0);
if ($cid > 0) { $ids[$cid] = true; }
}
}
}
} catch (Throwable $ePay) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals_submit'")->rowCount() > 0) {
$cols = ['user_id'];
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) { $cols[] = 'submitted_by_user'; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $cols[] = 'marketer_id'; }
$w = [];
$p = [];
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','submitted_by_user')) { $w[] = "submitted_by_user = ?"; $p[] = $userId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals_submit','marketer_id')) { $w[] = "marketer_id = ?"; $p[] = $userId; }
if (empty($w)) { $w[] = "1=0"; }
$where = "(" . implode(" OR ", $w) . ")";
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals_submit','company_id')) { $where .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$ds = $pdo->prepare("SELECT " . implode(',', $cols) . " FROM deals_submit WHERE " . $where);
$ds->execute($p);
foreach ($ds->fetchAll(PDO::FETCH_ASSOC) ?: [] as $r) {
$cid = (int)($r['user_id'] ?? 0);
if ($cid > 0) { $ids[$cid] = true; }
}
}
} catch (Throwable $eDs) {}
try {
if ($pdo->query("SHOW TABLES LIKE 'deals'")->rowCount() > 0) {
$cols = ['user_id'];
if (function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) { $cols[] = 'marketer_id'; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','submitted_by')) { $cols[] = 'submitted_by'; }
$w = [];
$p = [];
if (function_exists('tableHasColumn') && tableHasColumn('deals','marketer_id')) { $w[] = "marketer_id = ?"; $p[] = $userId; }
if (function_exists('tableHasColumn') && tableHasColumn('deals','submitted_by')) { $w[] = "submitted_by = ?"; $p[] = $userId; }
if (empty($w)) { $w[] = "1=0"; }
$where = "(" . implode(" OR ", $w) . ")";
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('deals','company_id')) { $where .= " AND (company_id = ? OR company_id IS NULL)"; $p[] = $companyId; }
$dq = $pdo->prepare("SELECT " . implode(',', $cols) . " FROM deals WHERE " . $where);
$dq->execute($p);
foreach ($dq->fetchAll(PDO::FETCH_ASSOC) ?: [] as $r) {
$cid = (int)($r['user_id'] ?? 0);
if ($cid > 0) { $ids[$cid] = true; }
}
}
} catch (Throwable $eD) {}
$idList = array_keys($ids);
if (!empty($idList)) {
$place = implode(',', array_fill(0, count($idList), '?'));
$sql = "SELECT id, {$nameCol} AS name, email FROM users WHERE role = 'client' AND id IN ($place)";
$params = $idList;
if ($companyId && function_exists('tableHasColumn') && tableHasColumn('users','company_id')) { $sql .= " AND (company_id = ? OR company_id IS NULL)"; $params[] = $companyId; }
$sql .= " ORDER BY {$nameCol} ASC";
$clq = $pdo->prepare($sql);
$clq->execute($params);
$clients = $clq->fetchAll(PDO::FETCH_ASSOC) ?: [];
}
}
} catch (Throwable $e) { $clients = []; }
?>
<div class="container-fluid px-4">
<style>
@media (max-width: 575.98px) {
.deal-actions { width: 100%; }
.deal-actions .btn { width: 100%; }
.deal-footer > div { width: 100%; }
.deal-footer .deal-footer-right { width: 100%; }
.deal-footer .deal-footer-right .btn { flex: 1 1 0; }
#dealPrevBtn { width: 100%; }
#dealStepIndicator { width: 100%; text-align: center; }
.deal-input-group, .deal-sqm-group { flex-wrap: wrap; }
.deal-input-group > .form-control, .deal-sqm-group > .form-control { flex: 1 1 100%; width: 100%; }
.deal-input-group > .btn { flex: 1 1 100%; width: 100%; }
#propertyPickerModal .property-select-btn { width: 100%; }
}
.deal-footer{ position:relative; z-index:5000; }
.deal-footer-right{ position:relative; z-index:5001; }
</style>
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-2 mt-4 mb-4 deal-page-header">
<h1 class="h3 mb-0 text-gray-800">
<i class="fa-solid fa-file-invoice-dollar me-2"></i>
<?= $variant === 'contact' ? 'Contact Centre: Deal Submission' : 'Marketing: Deal Submission' ?>
</h1>
<div class="d-flex flex-column flex-sm-row align-items-stretch align-items-sm-center gap-2 deal-actions">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#quickDealModal"><i class="fa-solid fa-plus me-2"></i>New Deal Submission</button>
<?php if (in_array($role, ['finance','finance_officer','finance_manager'])): ?>
<a href="finance-payment-queue.php" class="btn btn-outline-secondary"><i class="fa-solid fa-list-check me-2"></i>Payment Verification Queue</a>
<?php endif; ?>
</div>
</div>
<?php if (!empty($success_msg)): ?><div class="alert alert-success"><?= htmlspecialchars($success_msg) ?></div><?php endif; ?>
<?php if (!empty($clientAccessSetup) && is_array($clientAccessSetup)): ?>
<div class="alert alert-info">
<div class="fw-bold mb-1">Client Login Details</div>
<div class="small">Email: <?= htmlspecialchars((string)($clientAccessSetup['email'] ?? '')) ?></div>
<div class="small">Password: <?= htmlspecialchars((string)($clientAccessSetup['password'] ?? '')) ?></div>
<?php if (!empty($clientAccessSetup['otp_code'])): ?>
<div class="small">One-time code: <?= htmlspecialchars((string)$clientAccessSetup['otp_code']) ?></div>
<?php endif; ?>
<div class="mt-2 d-flex flex-column flex-sm-row flex-wrap align-items-stretch align-items-sm-center gap-2">
<a class="btn btn-sm btn-outline-primary" href="<?= htmlspecialchars((string)($clientAccessSetup['login_link'] ?? 'login.php')) ?>">Open Login</a>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars((string)($clientAccessSetup['email'] ?? '')) ?>')">Copy Email</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars((string)($clientAccessSetup['password'] ?? '')) ?>')">Copy Password</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="navigator.clipboard.writeText('<?= htmlspecialchars((string)($clientAccessSetup['login_link'] ?? '')) ?>')">Copy Link</button>
</div>
</div>
<?php endif; ?>
<?php if (!empty($error_msg)): ?><div class="alert alert-danger"><?= htmlspecialchars($error_msg) ?></div><?php endif; ?>
<form id="dealForm" method="post" enctype="multipart/form-data" class="card shadow-sm rounded-3 mb-4">
<div class="card-header py-3 bg-navy text-white d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2">
<h6 class="m-0 font-weight-bold">Deal Submission</h6>
<div id="dealStatusSummary" class="small text-white-50"></div>
</div>
<div class="card-body">
<input type="hidden" name="submit_deal" value="1">
<input type="hidden" name="policy_acceptance" value="1">
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Client Details</strong></div>
<div class="card-body">
<input type="hidden" name="existing_deal_id" id="existing_deal_id_hidden" value="0">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Client</label>
<select name="client_id" id="client_id_select" class="form-select">
<option value="">Select client</option>
<?php foreach (($clients ?? []) as $c): ?>
<option value="<?= (int)$c['id'] ?>"><?= htmlspecialchars(($c['name'] ?? 'Unknown').' — '.($c['email'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Client’s Name (text)</label>
<input type="text" name="client_name" id="client_name_text" class="form-control" placeholder="e.g., Mr George hill">
<div class="form-text">Optional: type the client name exactly as provided.</div>
</div>
<div class="col-md-6">
<label class="form-label">Client Email (for dashboard)</label>
<input type="email" name="client_email" class="form-control" placeholder="e.g., client@example.com">
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Deal Details</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<?php if ($variant === 'contact'): ?>
<label class="form-label">Deal Source</label>
<input type="hidden" name="deal_source" value="Contact Centre">
<input type="text" class="form-control" value="Contact Centre" readonly>
<div class="form-text">This page is tailored for Contact Centre submissions.</div>
<?php else: ?>
<label class="form-label">Deal Source</label>
<select name="deal_source" class="form-select" required>
<option <?= 'Marketer' ? 'selected' : '' ?>>Marketer</option>
<option>Contact Centre</option>
<option>Internal Staff</option>
<option>Referral</option>
<option>Direct Client</option>
</select>
<div class="form-text">Default is Marketer for Marketing submissions.</div>
<?php endif; ?>
</div>
<div class="col-md-6">
<label class="form-label"><?= $variant === 'contact' ? 'Contact Centre Rep Name' : 'Marketer Name' ?></label>
<input type="text" name="marketer_name" class="form-control" placeholder="<?= $variant === 'contact' ? 'e.g., Contact Rep Full Name' : 'e.g., Onyeugo Nkiruka Peace' ?>">
</div>
<div class="col-12">
<label class="form-label">Project / Property Description</label>
<div class="input-group deal-input-group">
<input type="text" name="project_desc" id="project_desc_input" class="form-control" placeholder="e.g., Half Hectare of 400sqm @ Hutu Royal">
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#propertyPickerModal">Browse Properties</button>
</div>
<input type="hidden" name="property_id" id="property_id_input" value="">
<div id="projectHistoryMsg" class="small mt-1"></div>
</div>
<div class="col-md-4">
<label class="form-label">Payment Plan</label>
<select name="plan_type" id="plan_type" class="form-select">
<option value="full">Full Payment</option>
<option value="3_months">3 Months Plan</option>
<option value="6_months">6 Months Plan</option>
<option value="custom">Custom Months</option>
</select>
</div>
<div class="col-md-4" id="custom_months_group" style="display:none;">
<label class="form-label">Custom Duration (months)</label>
<input type="number" min="1" step="1" name="custom_months" id="custom_months" class="form-control" placeholder="e.g., 12">
</div>
<div class="col-md-4">
<label class="form-label">Installment Start Date</label>
<input type="date" name="installment_start_date" id="installment_start_date" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">SQM</label>
<div class="input-group deal-sqm-group">
<input type="number" name="sqm" id="sqm_input" class="form-control" min="1" step="1" placeholder="e.g., 500">
<button type="button" class="btn btn-outline-secondary btn-sm" data-sqm-quick="250">250</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-sqm-quick="500">500</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-sqm-quick="1000">1000</button>
</div>
<div class="form-text">Type any SQM or tap a quick option.</div>
<div class="form-text" id="sqm_hectare_hint" style="display:none;"></div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Payment & Notes</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Payment Type</label>
<select name="payment_type" id="payment_type" class="form-select">
<option value="land" selected>Land</option>
<option value="infrastructure">Infrastructure</option>
<option value="excavation">Excavation</option>
</select>
<div class="form-text">Marketer/Contact Centre do not choose accounts. Account is assigned automatically by property + payment type.</div>
<div class="alert alert-warning mt-2 py-2 px-3 small" id="accountWarning" style="display:none;"></div>
</div>
<div class="col-12">
<div class="border rounded p-2 bg-light" id="balanceBreakdown" style="display:none;">
<div class="fw-bold text-navy mb-1">Outstanding Summary</div>
<div class="row g-2 small">
<div class="col-md-4"><span class="text-muted">Land:</span> <span id="bal_land">-</span></div>
<div class="col-md-4"><span class="text-muted">Infrastructure:</span> <span id="bal_infra">-</span></div>
<div class="col-md-4"><span class="text-muted">Excavation:</span> <span id="bal_excav">-</span></div>
</div>
</div>
</div>
<div class="col-md-4" id="offered_group">
<label class="form-label">Amount Offered to Client (if installment)</label>
<input type="text" name="amount_offered" class="form-control comma-format">
</div>
<div class="col-md-4" id="sofar_group">
<label class="form-label">Amount Paid So Far</label>
<input type="text" name="amount_paid_so_far" id="amount_paid_so_far" class="form-control comma-format">
<div id="prevPaymentsBreakdown" class="mt-2 small" style="display:none;">
<div class="fw-bold text-navy mb-1">Previous Payments:</div>
<ul class="list-group list-group-flush border rounded-3 overflow-hidden" id="prevPaymentsList" style="max-height:150px; overflow-y:auto;">
</ul>
</div>
</div>
<div class="col-md-4">
<label class="form-label">Amount Paid Now</label>
<input type="text" name="amount_paid_now" id="amount_paid_now" class="form-control comma-format">
</div>
<div class="col-12">
<label class="form-label">Client Payment Status / Notes</label>
<textarea name="client_payment_status" class="form-control" rows="2" placeholder="e.g., Client is still making payment"></textarea>
</div>
<div class="col-12">
<label class="form-label">Transaction History</label>
<div id="txnList" class="border rounded p-2 bg-light">
<div class="row g-2 align-items-end mb-2">
<div class="col-md-4"><input type="date" name="txn_date[]" class="form-control"></div>
<div class="col-md-4"><input type="text" name="txn_amount[]" class="form-control comma-format" placeholder="Amount"></div>
<div class="col-md-4"><button type="button" class="btn btn-outline-secondary btn-sm" onclick="addTxn()">Add Another</button></div>
</div>
</div>
<div class="form-text">Example: 20/1/2026 — 9M + 9M; 21/1/2026 — 1.86M + 9M; 23/1/2026 — 8.3M + 200k</div>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Discounts & Balance</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4" id="discount_group">
<label class="form-label">Discount Amount</label>
<input type="text" name="discount_amount" id="discount_amount" class="form-control comma-format">
</div>
<div class="col-md-4" id="discount_by_group">
<label class="form-label">Discount Approved By</label>
<input type="text" name="discount_approved_by" class="form-control" placeholder="Chairman / Admin / HR / Management">
</div>
<div class="col-md-4" id="balance_group">
<label class="form-label">Balance Remaining</label>
<input type="text" name="balance_remaining" id="balance_remaining" class="form-control comma-format" readonly>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3" id="commission_card">
<div class="card-header bg-white"><strong>Commission</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Total Commission %</label>
<input type="number" inputmode="decimal" step="0.01" min="0" max="100" name="commission_pct" class="form-control" value="5.00" id="commission_total_display">
</div>
<div class="col-md-3">
<label class="form-label"><?= $variant === 'contact' ? 'Contact Centre %' : 'Marketer %' ?></label>
<input type="number" inputmode="decimal" step="0.01" min="0" max="100" name="marketer_pct" id="marketer_pct" class="form-control" value="3.00">
<div class="form-text">Auto-balances with Agent % to match Total Commission %</div>
</div>
<div class="col-md-3">
<label class="form-label">Agent %</label>
<input type="number" inputmode="decimal" step="0.01" min="0" max="100" name="agent_pct" id="agent_pct" class="form-control" value="2.00">
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="btn-group w-100">
<button type="button" class="btn btn-outline-secondary" id="preset_3_2">3% / 2%</button>
<button type="button" class="btn btn-outline-secondary" id="preset_2_3">2% / 3%</button>
</div>
</div>
<div class="col-md-4">
<label class="form-label"><?= $variant === 'contact' ? 'Contact Centre Amount' : 'Marketer Amount' ?></label>
<input type="text" name="marketer_comm" id="marketer_comm" class="form-control comma-format" readonly>
</div>
<div class="col-md-4">
<label class="form-label">Agent Amount</label>
<input type="text" name="agent_comm" id="agent_comm" class="form-control comma-format" readonly>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Bank Details</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Marketer Account Number</label>
<input type="text" name="mk_acc_no" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Marketer Bank Name</label>
<input type="text" name="mk_bank" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Marketer Account Name</label>
<input type="text" name="mk_acc_name" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Agent Account Number</label>
<input type="text" name="ag_acc_no" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Agent Bank Name</label>
<input type="text" name="ag_bank" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">Agent Account Name</label>
<input type="text" name="ag_acc_name" class="form-control">
</div>
</div>
</div>
</div>
<div class="card shadow-sm mb-3">
<div class="card-header bg-white"><strong>Receipt Upload</strong></div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Upload Payment Receipt (Image/PDF)</label>
<input type="file" name="receipt" class="form-control" accept=".pdf,image/*" required>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-between align-items-center flex-wrap gap-2 deal-footer">
<div>
<button type="button" class="btn btn-outline-secondary" id="dealPrevBtn">Back</button>
</div>
<div class="text-muted small fw-semibold text-center text-sm-start" id="dealStepIndicator" aria-live="polite">Step 1 of 7 — Client Details</div>
<div class="d-flex gap-2 deal-footer-right">
<button type="button" class="btn btn-primary" id="dealNextBtn">Next</button>
<button type="button" class="btn btn-outline-primary" id="dealPreviewBtn" style="display:none;" data-bs-toggle="modal" data-bs-target="#dealPreviewModal">Preview</button>
<button type="submit" class="btn btn-success" id="dealSubmitBtn" style="pointer-events:auto;position:relative;z-index:1002">Submit Deal</button>
</div>
</div>
</form>
</div>
<!-- QUICK DEAL MODAL -->
<div class="modal fade" id="quickDealModal" tabindex="-1" aria-labelledby="quickDealLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down modal-dialog-scrollable">
<form class="modal-content" method="post" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title" id="quickDealLabel"><i class="fa-solid fa-file-invoice-dollar me-2"></i>New Deal Submission</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="quick_deal" value="1">
<div class="mb-2">
<label class="form-label">Client</label>
<select name="q_client_id" class="form-select" required>
<option value="">Select client</option>
<?php foreach (($clients ?? []) as $c): ?>
<option value="<?= (int)$c['id'] ?>"><?= htmlspecialchars(($c['name'] ?? 'Unknown').' — '.($c['email'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-2">
<label class="form-label">Property / Estate</label>
<input type="text" name="q_property" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">Amount Paid</label>
<input type="text" name="q_amount_paid" class="form-control comma-format" required>
</div>
<div class="mb-2">
<label class="form-label">Upload Payment Receipt</label>
<input type="file" name="q_receipt_file" class="form-control" accept=".pdf,image/*">
</div>
<div class="mb-2">
<label class="form-label">Payment Notes</label>
<textarea name="q_payment_notes" class="form-control" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
<?php if (isset($_GET['open']) && $_GET['open'] === 'quick_deal'): ?>
<script>document.addEventListener('DOMContentLoaded',function(){var m=new bootstrap.Modal(document.getElementById('quickDealModal'));m.show();});</script>
<?php endif; ?>
<script>
function addTxn(){
var c = document.getElementById('txnList');
var row = document.createElement('div');
row.className = 'row g-2 align-items-end mb-2';
row.innerHTML = `
<div class="col-md-4"><input type="date" name="txn_date[]" class="form-control"></div>
<div class="col-md-4"><input type="text" name="txn_amount[]" class="form-control comma-format" placeholder="Amount"></div>
<div class="col-md-4"><button type="button" class="btn btn-outline-danger btn-sm" onclick="this.closest('div.row').remove()">Remove</button></div>
`;
c.appendChild(row);
var newInp = row.querySelector('.comma-format');
if(newInp) {
newInp.addEventListener('input', function(e){
var val = e.target.value.replace(/,/g, '');
if(val !== '') {
var parts = val.split('.');
parts[0] = parts[0].replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if(parts.length > 2) parts = [parts[0], parts[1]];
e.target.value = parts.join('.');
}
});
}
}
(function(){
var paymentTypeSel = document.getElementById('payment_type');
var totalPctEl = document.getElementById('commission_total_display');
var statusSummary = document.getElementById('dealStatusSummary');
function numDec(v){
var s = String(v == null ? '' : v).trim().replace(',', '.');
var n = parseFloat(s);
return isFinite(n) ? n : 0;
}
function getTotalPct(){
var v = numDec((totalPctEl && totalPctEl.value) ? totalPctEl.value : '0');
if (!isFinite(v) || v < 0) v = 0;
if (v > 100) v = 100;
return Math.round(v * 100) / 100;
}
function recalc(){
var payType = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
var getVal = function(selector) {
var el = document.querySelector(selector);
return parseFloat(((el && el.value) ? el.value : '0').toString().replace(/,/g,''));
};
var offered = getVal('[name=amount_offered]');
var paidSoFar = getVal('[name=amount_paid_so_far]');
var paidNow = getVal('[name=amount_paid_now]');
var discount = getVal('[name=discount_amount]');
var plan = (document.getElementById('plan_type') && document.getElementById('plan_type').value) || 'installment';
var txnTotal = 0;
try {
var txns = document.querySelectorAll('input[name="txn_amount[]"]');
txns.forEach(function(inp){
var v = parseFloat(((inp && inp.value) ? inp.value : '0').toString().replace(/,/g,''));
if (isFinite(v) && v > 0) { txnTotal += v; }
});
} catch (e) {}
if (plan === 'full') {
paidSoFar = 0;
discount = 0;
offered = 0;
var offEl = document.querySelector('[name=amount_offered]');
if(offEl) offEl.value = '0.00';
var psfEl = document.getElementById('amount_paid_so_far');
if(psfEl) psfEl.value = '0.00';
}
var discounted = Math.max(offered - discount, 0);
var balance = plan === 'full' ? 0 : Math.max(discounted - (paidSoFar + paidNow), 0);
// Prevent negative balance display or accidental over-payment calculation
if (balance < 0) balance = 0;
var balEl = document.getElementById('balance_remaining');
if (balEl) {
balEl.value = formatWithCommas(balance.toFixed(2));
if (balance === 0 && (paidSoFar + paidNow) >= discounted && discounted > 0) {
balEl.classList.add('is-valid');
balEl.classList.remove('is-invalid');
if ((paidSoFar + paidNow) > discounted) {
balEl.classList.add('is-invalid');
if (statusSummary) {
statusSummary.innerHTML = '<span class="text-danger fw-bold"><i class="fa-solid fa-triangle-exclamation"></i> OVERPAYMENT: Amount exceeds offer balance!</span>';
}
}
} else {
balEl.classList.remove('is-valid');
balEl.classList.remove('is-invalid');
}
}
var baseNow = paidNow;
if (!isFinite(baseNow) || baseNow <= 0) {
if (isFinite(txnTotal) && txnTotal > 0) { baseNow = txnTotal; }
else if (isFinite(paidSoFar) && paidSoFar > 0) { baseNow = paidSoFar; }
else if (isFinite(offered) && offered > 0) { baseNow = offered; }
else { baseNow = 0; }
}
var mPctEl = document.getElementById('marketer_pct');
var aPctEl = document.getElementById('agent_pct');
var mCommEl = document.getElementById('marketer_comm');
var aCommEl = document.getElementById('agent_comm');
if (payType !== 'land') {
if (totalPctEl) totalPctEl.value = '0.00';
if (mPctEl) mPctEl.value = '0.00';
if (aPctEl) aPctEl.value = '0.00';
if (mCommEl) mCommEl.value = formatWithCommas('0.00');
if (aCommEl) aCommEl.value = formatWithCommas('0.00');
return;
}
var total = getTotalPct();
var mPct = numDec(mPctEl ? (mPctEl.value || '0') : '0');
var aPct = numDec(aPctEl ? (aPctEl.value || '0') : '0');
if (mPct < 0) mPct = 0; if (mPct > total) mPct = total;
if (aPct < 0) aPct = 0; if (aPct > total) aPct = total;
var active = null;
try { active = document.activeElement; } catch (e) { active = null; }
if (mPctEl && active !== mPctEl) mPctEl.value = mPct.toFixed(2);
if (aPctEl && active !== aPctEl) aPctEl.value = aPct.toFixed(2);
if (mCommEl) mCommEl.value = formatWithCommas((baseNow * (mPct/100)).toFixed(2));
if (aCommEl) aCommEl.value = formatWithCommas((baseNow * (aPct/100)).toFixed(2));
}
['amount_offered','amount_paid_so_far','amount_paid_now','discount_amount','commission_pct'].forEach(function(n){
var el = document.querySelector('[name='+n+']');
if (el) el.addEventListener('input', recalc);
});
['marketer_pct','agent_pct'].forEach(function(n){
var el = document.querySelector('[name='+n+']');
if (el) el.addEventListener('input', recalc);
});
var txnList = document.getElementById('txnList');
if (txnList) {
txnList.addEventListener('input', function(e){
if (e && e.target && e.target.name === 'txn_amount[]') { recalc(); }
});
}
var planSel = document.getElementById('plan_type');
if (planSel) {
planSel.addEventListener('change', function(){
var plan = planSel.value;
var soFar = document.getElementById('amount_paid_so_far');
var offeredEl = document.querySelector('[name=amount_offered]');
var discountEl = document.getElementById('discount_amount');
var discByEl = document.querySelector('[name=discount_approved_by]');
var offeredG = document.getElementById('offered_group');
var sofarG = document.getElementById('sofar_group');
var discountG = document.getElementById('discount_group');
var discountByG = document.getElementById('discount_by_group');
var balanceG = document.getElementById('balance_group');
var customG = document.getElementById('custom_months_group');
var customEl = document.getElementById('custom_months');
if (soFar) {
soFar.disabled = (plan === 'full');
}
if (offeredEl) offeredEl.disabled = (plan === 'full');
if (discountEl) discountEl.disabled = (plan === 'full');
if (discByEl) discByEl.disabled = (plan === 'full');
if (offeredG) offeredG.style.display = (plan === 'full') ? 'none' : '';
if (sofarG) sofarG.style.display = (plan === 'full') ? 'none' : '';
if (discountG) discountG.style.display = (plan === 'full') ? 'none' : '';
if (discountByG) discountByG.style.display = (plan === 'full') ? 'none' : '';
if (balanceG) balanceG.style.display = (plan === 'full') ? 'none' : '';
if (customG) customG.style.display = (plan === 'custom') ? '' : 'none';
if (customEl) {
customEl.disabled = (plan !== 'custom');
if (plan !== 'custom') customEl.value = '';
}
var startDateEl = document.getElementById('installment_start_date');
if (startDateEl) {
startDateEl.disabled = (plan === 'full');
var sdWrap = startDateEl.closest('.col-md-4');
if (sdWrap) sdWrap.style.display = (plan === 'full') ? 'none' : '';
}
if (plan === 'full') {
if (soFar) soFar.value = '0.00';
if (offeredEl) offeredEl.value = '0.00';
if (discountEl) discountEl.value = '0.00';
document.getElementById('balance_remaining').value = '0.00';
}
recalc();
});
planSel.dispatchEvent(new Event('change'));
}
var mPctEl = document.getElementById('marketer_pct');
var aPctEl = document.getElementById('agent_pct');
function balanceSplit(changed){
var payType = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
if (payType !== 'land') {
if (mPctEl) mPctEl.value = '0.00';
if (aPctEl) aPctEl.value = '0.00';
if (totalPctEl) totalPctEl.value = '0.00';
recalc();
return;
}
var total = getTotalPct();
var m = numDec(mPctEl.value || '0'); var a = numDec(aPctEl.value || '0');
if (m < 0) m = 0; if (m > total) m = total;
if (a < 0) a = 0; if (a > total) a = total;
if (changed === 'm') { a = total - m; }
else if (changed === 'a') { m = total - a; }
else { a = total - m; }
mPctEl.value = m.toFixed(2);
aPctEl.value = a.toFixed(2);
recalc();
}
if (mPctEl) {
mPctEl.addEventListener('change', function(){ balanceSplit('m'); });
mPctEl.addEventListener('blur', function(){ balanceSplit('m'); });
}
if (aPctEl) {
aPctEl.addEventListener('change', function(){ balanceSplit('a'); });
aPctEl.addEventListener('blur', function(){ balanceSplit('a'); });
}
if (totalPctEl) {
totalPctEl.addEventListener('change', function(){ balanceSplit('t'); });
totalPctEl.addEventListener('blur', function(){ balanceSplit('t'); });
}
var p32 = document.getElementById('preset_3_2');
var p23 = document.getElementById('preset_2_3');
if (p32) p32.addEventListener('click', function(){ var t=getTotalPct(); var m=Math.min(3.00,t); mPctEl.value=m.toFixed(2); aPctEl.value=Math.max(0,t-m).toFixed(2); recalc(); });
if (p23) p23.addEventListener('click', function(){ var t=getTotalPct(); var m=Math.min(2.00,t); mPctEl.value=m.toFixed(2); aPctEl.value=Math.max(0,t-m).toFixed(2); recalc(); });
// Comma formatting logic
function formatWithCommas(val) {
if (!val && val !== 0) return '';
var parts = val.toString().replace(/,/g, '').split('.');
parts[0] = parts[0].replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if (parts.length > 2) parts = [parts[0], parts[1]];
return parts.join('.');
}
document.querySelectorAll('.comma-format').forEach(function(el) {
el.addEventListener('input', function(e) {
var start = e.target.selectionStart;
var oldLen = e.target.value.length;
e.target.value = formatWithCommas(e.target.value);
var newLen = e.target.value.length;
e.target.setSelectionRange(start + (newLen - oldLen), start + (newLen - oldLen));
});
// Initial format if has value
if (el.value) el.value = formatWithCommas(el.value);
});
try {
window.dealFormatWithCommas = formatWithCommas;
window.dealRecalc = recalc;
} catch (e) {}
})();
document.addEventListener('DOMContentLoaded', function(){
var form = document.getElementById('dealForm');
if (form) {
var submitBtn = document.getElementById('dealSubmitBtn');
var submitBtnDefaultText = submitBtn ? (submitBtn.textContent || 'Submit Deal') : 'Submit Deal';
window.addEventListener('pageshow', function(ev){
if (!submitBtn) return;
if (ev && ev.persisted) {
submitBtn.disabled = false;
submitBtn.textContent = submitBtnDefaultText;
}
});
var statusSummary = document.getElementById('dealStatusSummary');
function validateStep(i){
var ok = true;
function req(name){ var el = form.querySelector('[name='+name+']'); if (!el) return true; var v = (el.value||'').trim(); var good = v!==''; if (!good) { el.classList.add('is-invalid'); ok=false; } else { el.classList.remove('is-invalid'); } return good; }
if (i===0) { var a = form.querySelector('select[name=client_id]'); var b = form.querySelector('[name=client_name]'); var c = form.querySelector('[name=client_email]'); var has = (a && a.value.trim()!=='') || (b && b.value.trim()!=='') || (c && c.value.trim()!==''); if (!has) { ok=false; alert('Please select a client or provide client name/email.'); } }
if (i===1) {
req('marketer_name'); req('project_desc'); req('plan_type');
var p = (form.querySelector('[name=plan_type]') || { value: '' }).value;
if (p === 'custom') { req('custom_months'); }
if (p !== 'full') { req('installment_start_date'); }
}
if (i===2) { req('amount_paid_now'); }
if (i===4) {
var pt = (typeof paymentTypeSel !== 'undefined' && paymentTypeSel) ? (paymentTypeSel.value || 'land') : 'land';
if (pt !== 'land') { return ok; }
var m = parseFloat((form.querySelector('[name=marketer_pct]')||{value:'0'}).value||'0');
var a = parseFloat((form.querySelector('[name=agent_pct]')||{value:'0'}).value||'0');
var t = parseFloat((form.querySelector('[name=commission_pct]')||{value:'0'}).value||'0');
if (!isFinite(t) || t < 0) t = 0;
if (t > 100) t = 100;
var sum = (m + a).toFixed(2);
if (sum !== t.toFixed(2)) { ok=false; alert('Commission split must sum to the Total Commission %.'); }
}
return ok;
}
function doAjaxSubmit(){
var pt = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
var existingId = existingDealIdInp ? Number(existingDealIdInp.value || '0') : 0;
if (pt !== 'land' && (!existingId || existingId <= 0)) {
var m0 = 'Please select an existing deal/property record before submitting an ' + pt + ' payment.';
if (statusSummary) { statusSummary.textContent = m0; }
alert(m0);
return;
}
if (accountWarning && accountWarning.style.display !== 'none' && (accountWarning.textContent || '').trim() !== '') {
var m1 = (accountWarning.textContent || 'Missing bank account configuration.');
if (statusSummary) { statusSummary.textContent = m1; }
alert(m1);
return;
}
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Processing...'; }
if (statusSummary) { statusSummary.textContent = 'Submitting...'; }
var data = new FormData(form);
data.append('xhr', '1');
data.append('submit_deal', '1');
var controller = null;
try { controller = new AbortController(); } catch (err) { controller = null; }
var timeoutMs = 45000;
var timeoutId = setTimeout(function(){
try { if (controller) controller.abort(); } catch (err) {}
}, timeoutMs);
fetch(form.getAttribute('action') || window.location.href, {
method: 'POST',
body: data,
signal: controller ? controller.signal : undefined
}).then(function(r){
return r.text().then(function(txt){
var raw = (txt || '').trim();
var jsonText = raw;
var firstBrace = raw.indexOf('{');
if (firstBrace > 0) { jsonText = raw.slice(firstBrace); }
try {
var parsed = JSON.parse(jsonText);
if (parsed && typeof parsed === 'object') return parsed;
} catch (e) {}
var msg = 'Invalid server response.';
if (r && typeof r.status === 'number' && r.status > 0) { msg += ' HTTP ' + r.status + '.'; }
if (raw) { msg += ' ' + raw.slice(0, 180); }
return { success: false, error: msg };
}).catch(function(){
return { success: false, error: 'Invalid server response.' };
});
}).then(function(res){
if (!res) res = { success: false, error: 'No response.' };
if (res.success) {
if (statusSummary) { statusSummary.textContent = res.message || 'Submitted.'; }
window.location.href = 'deal-submission.php?success=1';
return;
}
var msg = res.error || res.message || 'Submission failed.';
if (statusSummary) { statusSummary.textContent = msg; }
alert(msg);
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = submitBtnDefaultText; }
}).catch(function(err){
var msg = (err && err.name === 'AbortError') ? 'Submission timed out. Please try again.' : 'Network error. Please try again.';
if (statusSummary) { statusSummary.textContent = msg; }
alert(msg);
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = submitBtnDefaultText; }
}).finally(function(){
try { clearTimeout(timeoutId); } catch (err) {}
});
}
function resolveExistingDealId(){
var cid = cidSelect ? (cidSelect.value || '').trim() : '';
var pid = propIdInp ? (propIdInp.value || '').trim() : '';
var pdesc = (pDescInp ? pDescInp.value : '').trim();
if (!cid || !pid) return Promise.resolve(0);
var qs = 'client_id=' + encodeURIComponent(cid) + '&property_id=' + encodeURIComponent(pid);
if (pdesc) { qs += '&project_desc=' + encodeURIComponent(pdesc); }
return fetch('ajax_get_client_deal_status.php?' + qs).then(function(r){ return r.json(); }).then(function(res){
var id = 0;
try { id = res && res.success && res.data ? Number(res.data.parent_deal_id || 0) : 0; } catch (e) { id = 0; }
if (!isFinite(id) || id < 0) id = 0;
return id;
}).catch(function(){ return 0; });
}
form.addEventListener('submit', function(e){
if (!validateStep(0) || !validateStep(1) || !validateStep(2) || !validateStep(4)) {
e.preventDefault();
return;
}
var receiptEl = form.querySelector('[name="receipt"]');
var hasReceipt = false;
try { hasReceipt = !!(receiptEl && receiptEl.files && receiptEl.files.length > 0); } catch (err) { hasReceipt = false; }
if (!hasReceipt) {
e.preventDefault();
var receiptIdx = -1;
try {
if (Array.isArray(steps) && receiptEl) {
for (var si = 0; si < steps.length; si++) {
if (steps[si] && steps[si].contains && steps[si].contains(receiptEl)) { receiptIdx = si; break; }
}
}
} catch (err2) { receiptIdx = -1; }
if (receiptIdx >= 0) { idx = receiptIdx; show(idx); }
if (statusSummary) { statusSummary.textContent = 'Please upload the payment receipt before submitting.'; }
alert('Please upload the payment receipt before submitting.');
try { if (receiptEl && receiptEl.scrollIntoView) receiptEl.scrollIntoView({behavior:'smooth', block:'center'}); } catch (err3) {}
return;
}
e.preventDefault();
var pt = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
var existingId = existingDealIdInp ? Number(existingDealIdInp.value || '0') : 0;
if (pt !== 'land' && (!existingId || existingId <= 0)) {
if (statusSummary) { statusSummary.textContent = 'Checking existing land deal for this property...'; }
resolveExistingDealId().then(function(id){
if (id > 0 && existingDealIdInp) { existingDealIdInp.value = String(id); }
if ((!id || id <= 0) && statusSummary) {
statusSummary.textContent = 'No existing land deal found for this client + property. Submit a Land payment first, then submit ' + pt + '.';
}
doAjaxSubmit();
});
return;
}
doAjaxSubmit();
});
}
var steps = Array.prototype.slice.call(document.querySelectorAll('#dealForm .card.shadow-sm.mb-3'));
if (steps.length === 0) { steps = Array.prototype.slice.call(document.querySelectorAll('#dealForm .card')); }
if (steps.length === 0) { steps = Array.prototype.slice.call(document.querySelectorAll('#dealForm [data-step], #dealForm .step')); }
var prev = document.getElementById('dealPrevBtn');
var next = document.getElementById('dealNextBtn');
var submit = document.getElementById('dealSubmitBtn');
var stepIndicator = document.getElementById('dealStepIndicator');
var idx = 0;
// --- Dynamic Check for Existing Balance ---
var cidSelect = document.getElementById('client_id_select');
var pDescInp = document.getElementById('project_desc_input');
var amtOfferedInp = document.querySelector('[name=amount_offered]');
var amtSoFarInp = document.getElementById('amount_paid_so_far');
var statusSummary = document.getElementById('dealStatusSummary');
var historyMsg = document.getElementById('projectHistoryMsg');
var prevBreakdown = document.getElementById('prevPaymentsBreakdown');
var prevList = document.getElementById('prevPaymentsList');
var existingDealIdInp = document.getElementById('existing_deal_id_hidden');
var paymentTypeSel = document.getElementById('payment_type');
var propIdInp = document.getElementById('property_id_input');
var balWrap = document.getElementById('balanceBreakdown');
var balLand = document.getElementById('bal_land');
var balInfra = document.getElementById('bal_infra');
var balExcav = document.getElementById('bal_excav');
var accountWarning = document.getElementById('accountWarning');
function fmtMoney(val) {
var n = Number(val || 0);
if (!isFinite(n)) n = 0;
return '₦' + n.toLocaleString(undefined, {minimumFractionDigits:2, maximumFractionDigits:2});
}
var fmtCommas = (typeof window.dealFormatWithCommas === 'function') ? window.dealFormatWithCommas : function(val){
if (!val && val !== 0) return '';
var parts = val.toString().replace(/,/g, '').split('.');
parts[0] = parts[0].replace(/\D/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if (parts.length > 2) parts = [parts[0], parts[1]];
return parts.join('.');
};
function safeRecalc(){
try { if (typeof window.dealRecalc === 'function') window.dealRecalc(); } catch (e) {}
}
function refreshBalances() {
var cid = cidSelect ? (cidSelect.value || '').trim() : '';
var pid = propIdInp ? (propIdInp.value || '').trim() : '';
if (!cid || !pid) {
if (balWrap) balWrap.style.display = 'none';
if (accountWarning) { accountWarning.style.display = 'none'; accountWarning.textContent = ''; }
return;
}
fetch('deal-submission.php?action=balances&client_id=' + encodeURIComponent(cid) + '&property_id=' + encodeURIComponent(pid))
.then(function(r){ return r.json(); })
.then(function(res){
if (!res || !res.ok) { if (balWrap) balWrap.style.display = 'none'; return; }
if (balWrap) balWrap.style.display = '';
if (balLand) balLand.textContent = fmtMoney((res.land && res.land.balance) ? res.land.balance : 0) + ' / ' + fmtMoney((res.land && res.land.total) ? res.land.total : 0);
if (balInfra) balInfra.textContent = fmtMoney((res.infrastructure && res.infrastructure.balance) ? res.infrastructure.balance : 0) + ' / ' + fmtMoney((res.infrastructure && res.infrastructure.total) ? res.infrastructure.total : 0);
if (balExcav) balExcav.textContent = fmtMoney((res.excavation && res.excavation.balance) ? res.excavation.balance : 0) + ' / ' + fmtMoney((res.excavation && res.excavation.total) ? res.excavation.total : 0);
var t = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
var ok = true;
if (res.accounts && Object.prototype.hasOwnProperty.call(res.accounts, t)) { ok = !!res.accounts[t]; }
if (!ok) {
if (accountWarning) {
accountWarning.textContent = 'No active ' + t + ' bank account configured for this property. Go to Finance → Property Bank Accounts and save the ' + t + ' account as Active.';
accountWarning.style.display = '';
}
} else {
if (accountWarning) { accountWarning.style.display = 'none'; accountWarning.textContent = ''; }
}
})
.catch(function(){ if (balWrap) balWrap.style.display = 'none'; if (accountWarning) { accountWarning.style.display = 'none'; accountWarning.textContent = ''; } });
}
function applyPaymentTypeUi() {
var t = paymentTypeSel ? (paymentTypeSel.value || 'land') : 'land';
var planSel = document.getElementById('plan_type');
if (!planSel) return;
if (t !== 'land') {
planSel.value = 'full';
planSel.disabled = true;
planSel.dispatchEvent(new Event('change'));
} else {
planSel.disabled = false;
planSel.dispatchEvent(new Event('change'));
}
var cc = document.getElementById('commission_card');
if (cc) { cc.style.display = (t === 'land') ? '' : 'none'; }
['commission_pct','marketer_pct','agent_pct','marketer_comm','agent_comm'].forEach(function(n){
var el = document.querySelector('[name='+n+']');
if (!el) return;
if (t === 'land') {
el.disabled = false;
} else {
el.value = '0.00';
el.disabled = true;
}
});
}
function checkClientStatus() {
var cid = cidSelect ? cidSelect.value : '';
var pdesc = (pDescInp ? pDescInp.value : '').trim();
if (!cid) {
if (statusSummary) statusSummary.textContent = '';
if (historyMsg) historyMsg.innerHTML = '';
if (prevBreakdown) prevBreakdown.style.display = 'none';
if (existingDealIdInp) existingDealIdInp.value = '0';
return;
}
var pid = (propIdInp ? (propIdInp.value || '').trim() : '');
var qs = 'client_id=' + encodeURIComponent(cid);
if (pid) { qs += '&property_id=' + encodeURIComponent(pid); }
if (pdesc) { qs += '&project_desc=' + encodeURIComponent(pdesc); }
fetch('ajax_get_client_deal_status.php?' + qs)
.then(res => res.json())
.then(res => {
if (res.success && res.data) {
var d = res.data;
if (d.deals_count > 0) {
if (existingDealIdInp) existingDealIdInp.value = d.parent_deal_id || '0';
var msg = "Client has " + d.deals_count + " record(s) matching this property. ";
if (d.total_paid > 0) {
msg += "Total Paid: ₦" + fmtCommas(d.total_paid.toFixed(2)) + ". ";
}
if (d.total_offered > 0) {
if (amtOfferedInp && (!amtOfferedInp.value || amtOfferedInp.value == '0.00')) {
amtOfferedInp.value = fmtCommas(d.total_offered.toFixed(2));
}
if (amtSoFarInp) {
amtSoFarInp.value = fmtCommas(d.total_paid.toFixed(2));
}
var rem = d.total_offered - d.total_paid;
if (rem <= 0 && d.total_offered > 0) {
historyMsg.innerHTML = '<span class="badge bg-success"><i class="fa-solid fa-check-circle me-1"></i> FULLY PAID</span>';
} else {
historyMsg.innerHTML = '<span class="text-info small"><i class="fa-solid fa-circle-info me-1"></i> Found matching property. Suggested remaining balance: ₦' + fmtCommas(rem.toFixed(2)) + '</span>';
}
}
// Populate Breakdown List
if (prevList && res.raw_deals) {
prevList.innerHTML = '';
res.raw_deals.forEach(function(rd){
var li = document.createElement('li');
li.className = 'list-group-item d-flex justify-content-between align-items-center py-1';
li.innerHTML = '<span>' + (rd.project_name || rd.project_desc || 'Payment') + '</span>' +
'<span class="fw-bold text-navy">₦' + fmtCommas(parseFloat(rd.amount_paid_so_far || 0).toFixed(2)) + '</span>';
prevList.appendChild(li);
});
if (prevBreakdown) prevBreakdown.style.display = '';
}
if (statusSummary) statusSummary.textContent = msg;
safeRecalc();
refreshBalances();
} else {
if (statusSummary) statusSummary.textContent = pdesc ? 'No existing deal found for this property description.' : 'Select property to check balance.';
if (historyMsg) historyMsg.innerHTML = '';
if (prevBreakdown) prevBreakdown.style.display = 'none';
}
}
})
.catch(err => console.error("Error checking client status", err));
}
if (cidSelect) cidSelect.addEventListener('change', checkClientStatus);
if (pDescInp) pDescInp.addEventListener('blur', checkClientStatus);
if (cidSelect) cidSelect.addEventListener('change', refreshBalances);
if (propIdInp) propIdInp.addEventListener('change', refreshBalances);
if (propIdInp) propIdInp.addEventListener('change', checkClientStatus);
if (paymentTypeSel) paymentTypeSel.addEventListener('change', function(){ applyPaymentTypeUi(); refreshBalances(); checkClientStatus(); });
applyPaymentTypeUi();
var pickerModalEl = document.getElementById('propertyPickerModal');
var propsTbodyEl = document.getElementById('pickerPropsTbody');
var propSearchEl = document.getElementById('pickerPropSearch');
function renderProperties(rows) {
if (!propsTbodyEl) return;
propsTbodyEl.innerHTML = '';
if (!rows || !rows.length) {
propsTbodyEl.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No properties found.</td></tr>';
return;
}
rows.forEach(function(p){
var tr = document.createElement('tr');
var id = p.id || '';
var title = (p.title || '').toString();
var location = (p.location || p.address || '').toString();
var price = (p.price !== null && p.price !== undefined) ? parseFloat(p.price || 0) : null;
var status = (p.status || '').toString();
var sqmVal = (p.area_sqm || p.total_sqm || '').toString();
var tdId = document.createElement('td'); tdId.textContent = String(id); tdId.setAttribute('data-label', 'ID');
var tdTitle = document.createElement('td'); tdTitle.textContent = title; tdTitle.setAttribute('data-label', 'Title');
var tdLoc = document.createElement('td'); tdLoc.textContent = location || '—'; tdLoc.setAttribute('data-label', 'Location');
var tdPrice = document.createElement('td');
tdPrice.textContent = (price !== null) ? ('₦' + price.toLocaleString(undefined, {minimumFractionDigits:2, maximumFractionDigits:2})) : '—';
tdPrice.setAttribute('data-label', 'Price');
var tdSt = document.createElement('td'); tdSt.textContent = status; tdSt.setAttribute('data-label', 'Status');
var tdAct = document.createElement('td');
tdAct.setAttribute('data-label', 'Action');
var b = document.createElement('button');
b.type = 'button';
b.className = 'btn btn-sm btn-outline-primary property-select-btn';
b.setAttribute('data-id', String(id));
b.setAttribute('data-title', title);
b.setAttribute('data-sqm', sqmVal);
b.textContent = 'Use';
tdAct.appendChild(b);
tr.appendChild(tdId);
tr.appendChild(tdTitle);
tr.appendChild(tdLoc);
tr.appendChild(tdPrice);
tr.appendChild(tdSt);
tr.appendChild(tdAct);
propsTbodyEl.appendChild(tr);
});
}
function fetchJson(url) {
return fetch(url, { credentials: 'same-origin' }).then(function(r){ return r.json(); });
}
function loadProperties() {
var q = propSearchEl ? (propSearchEl.value || '').trim() : '';
var url = 'deal-submission.php?action=picker_properties&search=' + encodeURIComponent(q);
return fetchJson(url)
.then(function(res){
if (!res || !res.ok) { renderProperties([]); return; }
renderProperties(res.rows || []);
})
.catch(function(){ renderProperties([]); });
}
if (propSearchEl) {
propSearchEl.addEventListener('input', function(){
loadProperties();
});
}
if (propsTbodyEl) {
propsTbodyEl.addEventListener('click', function(e){
var btn = e.target.closest('.property-select-btn');
if (!btn) return;
var id = btn.getAttribute('data-id') || '';
var title = btn.getAttribute('data-title') || '';
var sqmVal = btn.getAttribute('data-sqm') || '';
var descInp = document.getElementById('project_desc_input');
var propInp = document.getElementById('property_id_input');
var sqmInp = document.getElementById('sqm_input');
if (descInp && title) descInp.value = title;
if (propInp) propInp.value = id;
if (sqmInp && sqmVal && (!sqmInp.value || String(sqmInp.value).trim() === '')) sqmInp.value = sqmVal;
if (pickerModalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
var inst = bootstrap.Modal.getInstance(pickerModalEl) || new bootstrap.Modal(pickerModalEl);
inst.hide();
}
checkClientStatus();
refreshBalances();
});
}
if (pickerModalEl) {
pickerModalEl.addEventListener('shown.bs.modal', function(){
loadProperties();
});
}
var sqmQuick = document.querySelectorAll('[data-sqm-quick]');
var sqmHint = document.getElementById('sqm_hectare_hint');
function updateSqmHint(){
var sqmInp = document.getElementById('sqm_input');
if (!sqmInp || !sqmHint) return;
var v = parseFloat(String(sqmInp.value || '').trim());
if (!isFinite(v) || v <= 0 || v < 1000) {
sqmHint.style.display = 'none';
sqmHint.textContent = '';
return;
}
var ha = v / 10000;
var fmt = (Math.round(ha * 100) / 100).toFixed(2);
sqmHint.textContent = 'Hectares: ' + fmt + ' ha';
sqmHint.style.display = '';
}
sqmQuick.forEach(function(btn){
btn.addEventListener('click', function(){
var v = this.getAttribute('data-sqm-quick') || '';
var sqmInp = document.getElementById('sqm_input');
if (sqmInp && v) {
sqmInp.value = v;
sqmInp.focus();
updateSqmHint();
}
});
});
var sqmInpLive = document.getElementById('sqm_input');
if (sqmInpLive) sqmInpLive.addEventListener('input', updateSqmHint);
updateSqmHint();
function show(i){
var total = steps.length;
if (total === 0) {
if (prev) prev.style.display = 'none';
if (next) next.style.display = 'none';
if (previewBtn) previewBtn.style.display = 'none';
if (submit) submit.style.display = '';
if (stepIndicator) stepIndicator.textContent = 'Single step form';
return;
}
steps.forEach(function(c,k){ c.style.display = (k===i)? '' : 'none'; });
if (prev) prev.disabled = (i===0);
if (stepIndicator) {
var stepTitle = '';
var header = steps[i] ? steps[i].querySelector('.card-header strong, .card-header') : null;
if (header) {
stepTitle = (header.textContent || '').trim().replace(/\s+/g, ' ');
}
stepIndicator.textContent = 'Step ' + (i + 1) + ' of ' + total + (stepTitle ? ' — ' + stepTitle : '');
}
if (next) {
next.style.display = (i < total-1) ? '' : 'none';
next.textContent = 'Next';
}
if (previewBtn) {
previewBtn.style.display = (i === total-1) ? '' : 'none';
}
if (submit) {
submit.style.display = (i === total-1) ? '' : 'none';
submit.disabled = false;
}
var top = form.querySelector('.card-header'); if (top) top.scrollIntoView({behavior:'smooth',block:'start'});
}
show(idx);
if (next) next.addEventListener('click', function(){ if (idx < steps.length-1) { idx++; show(idx); } });
if (prev) prev.addEventListener('click', function(){ if (idx > 0) { idx--; show(idx); } });
var previewBtn = document.getElementById('dealPreviewBtn');
var previewBody = document.getElementById('dealPreviewBody');
var previewSubmitBtn = document.getElementById('dealPreviewSubmitBtn');
var previewModalEl = document.getElementById('dealPreviewModal');
var variant = <?= json_encode($variant) ?>;
function escHtml(s){
return String(s || '').replace(/[&<>"']/g, function(c){
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);
});
}
function valSel(el){
if (!el) return '';
var tag = (el.tagName || '').toLowerCase();
if (tag === 'select') {
var opt = el.options && el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null;
return (opt && opt.textContent) ? String(opt.textContent).trim() : '';
}
if (el.type === 'checkbox') { return el.checked ? 'Yes' : 'No'; }
return String(el.value || '').trim();
}
function getById(id){ return document.getElementById(id); }
function getByName(name){ return form ? form.querySelector('[name="' + name + '"]') : null; }
function row(label, value){
if (!value) return '';
return '<div class="d-flex justify-content-between gap-3 py-1 border-bottom">'
+ '<div class="text-muted small">' + escHtml(label) + '</div>'
+ '<div class="fw-semibold text-end" style="max-width: 65%; word-break: break-word;">' + escHtml(value) + '</div>'
+ '</div>';
}
function section(title, rowsHtml){
if (!rowsHtml) return '';
return '<div class="card shadow-sm mb-3">'
+ '<div class="card-header bg-white"><strong>' + escHtml(title) + '</strong></div>'
+ '<div class="card-body py-2">' + rowsHtml + '</div>'
+ '</div>';
}
function buildPreview(){
if (!previewBody) return;
var html = '';
var clientName = valSel(getByName('client_name'));
var clientEmail = valSel(getByName('client_email'));
var clientSel = valSel(getById('client_id_select'));
html += section('Client Details',
row('Client', clientSel) +
row('Client’s Name (text)', clientName) +
row('Client Email', clientEmail)
);
var dealSource = valSel(getByName('deal_source'));
var marketerLabel = (variant === 'contact') ? 'Contact Centre Rep Name' : 'Marketer Name';
var marketerName = valSel(getByName('marketer_name'));
var projectDesc = valSel(getById('project_desc_input'));
var propertyId = valSel(getById('property_id_input'));
var planType = valSel(getById('plan_type'));
var customMonths = valSel(getById('custom_months'));
var instStart = valSel(getById('installment_start_date'));
var sqm = valSel(getById('sqm_input'));
html += section('Deal Details',
row('Deal Source', dealSource) +
row(marketerLabel, marketerName) +
row('Project / Property Description', projectDesc) +
row('Property ID', propertyId) +
row('Payment Plan', planType) +
row('Custom Duration (months)', customMonths) +
row('Installment Start Date', instStart) +
row('SQM', sqm)
);
var paymentType = valSel(getById('payment_type'));
var offered = valSel(getByName('amount_offered'));
var paidSoFar = valSel(getByName('amount_paid_so_far'));
var paidNow = valSel(getByName('amount_paid_now'));
var notes = valSel(getByName('client_payment_status'));
html += section('Payment & Notes',
row('Payment Type', paymentType) +
row('Amount Offered', offered) +
row('Amount Paid So Far', paidSoFar) +
row('Amount Paid Now', paidNow) +
row('Notes', notes)
);
var txnDates = form ? form.querySelectorAll('input[name="txn_date[]"]') : [];
var txnAmounts = form ? form.querySelectorAll('input[name="txn_amount[]"]') : [];
var txnRows = '';
var txnCount = Math.max(txnDates.length, txnAmounts.length);
for (var i = 0; i < txnCount; i++) {
var d = txnDates[i] ? String(txnDates[i].value || '').trim() : '';
var a = txnAmounts[i] ? String(txnAmounts[i].value || '').trim() : '';
if (!d && !a) continue;
txnRows += '<tr>'
+ '<td data-label="Date" class="text-muted small">' + escHtml(d || '-') + '</td>'
+ '<td data-label="Amount" class="text-end fw-semibold">' + escHtml(a || '-') + '</td>'
+ '</tr>';
}
if (txnRows) {
html += '<div class="card shadow-sm mb-3">'
+ '<div class="card-header bg-white"><strong>Transaction History</strong></div>'
+ '<div class="card-body p-0">'
+ '<div class="table-responsive">'
+ '<table class="table table-sm mb-0 table-stackable">'
+ '<thead class="table-light"><tr><th>Date</th><th class="text-end">Amount</th></tr></thead>'
+ '<tbody>' + txnRows + '</tbody>'
+ '</table>'
+ '</div>'
+ '</div>'
+ '</div>';
}
var discountAmt = valSel(getByName('discount_amount'));
var discountBy = valSel(getByName('discount_approved_by'));
var balance = valSel(getByName('balance_remaining'));
html += section('Discounts & Balance',
row('Discount Amount', discountAmt) +
row('Discount Approved By', discountBy) +
row('Balance Remaining', balance)
);
var commissionTotal = valSel(getById('commission_total_display'));
var marketerPct = valSel(getById('marketer_pct'));
var agentPct = valSel(getById('agent_pct'));
var marketerComm = valSel(getById('marketer_comm'));
var agentComm = valSel(getById('agent_comm'));
html += section('Commission',
row('Total Commission %', commissionTotal) +
row((variant === 'contact') ? 'Contact Centre %' : 'Marketer %', marketerPct) +
row('Agent %', agentPct) +
row((variant === 'contact') ? 'Contact Centre Amount' : 'Marketer Amount', marketerComm) +
row('Agent Amount', agentComm)
);
var mkAcc = valSel(getByName('mk_acc_no'));
var mkBank = valSel(getByName('mk_bank'));
var mkName = valSel(getByName('mk_acc_name'));
var agAcc = valSel(getByName('ag_acc_no'));
var agBank = valSel(getByName('ag_bank'));
var agName = valSel(getByName('ag_acc_name'));
html += section('Bank Details',
row((variant === 'contact') ? 'Contact Centre Account Number' : 'Marketer Account Number', mkAcc) +
row((variant === 'contact') ? 'Contact Centre Bank Name' : 'Marketer Bank Name', mkBank) +
row((variant === 'contact') ? 'Contact Centre Account Name' : 'Marketer Account Name', mkName) +
row('Agent Account Number', agAcc) +
row('Agent Bank Name', agBank) +
row('Agent Account Name', agName)
);
var receiptEl = getByName('receipt');
var receiptName = '';
try { receiptName = receiptEl && receiptEl.files && receiptEl.files[0] ? receiptEl.files[0].name : ''; } catch (e) { receiptName = ''; }
html += section('Receipt Upload', row('Receipt File', receiptName || 'Not uploaded'));
previewBody.innerHTML = html || '<div class="text-muted">Nothing to preview yet.</div>';
}
if (previewBtn && previewBody) {
previewBtn.addEventListener('click', function(){
buildPreview();
});
}
if (previewSubmitBtn) {
previewSubmitBtn.addEventListener('click', function(){
var rEl = form ? form.querySelector('[name="receipt"]') : null;
var okReceipt = false;
try { okReceipt = !!(rEl && rEl.files && rEl.files.length > 0); } catch (err) { okReceipt = false; }
if (!okReceipt) {
try {
if (previewModalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
var inst0 = bootstrap.Modal.getInstance(previewModalEl) || bootstrap.Modal.getOrCreateInstance(previewModalEl);
inst0.hide();
}
} catch (e0) {}
var receiptIdx2 = -1;
try {
if (Array.isArray(steps) && rEl) {
for (var sj = 0; sj < steps.length; sj++) {
if (steps[sj] && steps[sj].contains && steps[sj].contains(rEl)) { receiptIdx2 = sj; break; }
}
}
} catch (e1) { receiptIdx2 = -1; }
if (receiptIdx2 >= 0) { idx = receiptIdx2; show(idx); }
if (statusSummary) { statusSummary.textContent = 'Please upload the payment receipt before submitting.'; }
alert('Please upload the payment receipt before submitting.');
try { if (rEl && rEl.scrollIntoView) rEl.scrollIntoView({behavior:'smooth', block:'center'}); } catch (e2) {}
return;
}
try {
if (previewModalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
var inst = bootstrap.Modal.getInstance(previewModalEl) || bootstrap.Modal.getOrCreateInstance(previewModalEl);
inst.hide();
}
} catch (e) {}
if (submit) { submit.click(); return; }
if (form && typeof form.requestSubmit === 'function') { form.requestSubmit(); return; }
if (form) { form.submit(); }
});
}
});
</script>
<?php if (!$isAjax): ?>
<div class="modal fade" id="propertyPickerModal" tabindex="-1" aria-labelledby="propertyPickerLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-fullscreen-sm-down modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="propertyPickerLabel">Select Property</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center gap-2 mb-2">
<div class="small text-muted">Source: Properties</div>
<div class="small text-muted">New properties added will appear here automatically.</div>
</div>
<div class="d-flex gap-2 mb-2">
<input type="text" class="form-control form-control-sm" id="pickerPropSearch" placeholder="Search properties...">
</div>
<div class="table-responsive">
<table class="table table-hover align-middle table-sm table-stackable">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Location</th>
<th>Price</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody id="pickerPropsTbody">
<tr><td colspan="6" class="text-center text-muted">Type to search or scroll to select.</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="dealPreviewModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-fullscreen-sm-down modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fa-solid fa-eye me-2"></i>Deal Submission Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-info py-2 px-3 small mb-3">
Review the details below before submitting.
</div>
<div id="dealPreviewBody"></div>
</div>
<div class="modal-footer d-flex justify-content-between gap-2">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Back to Form</button>
<button type="button" class="btn btn-success" id="dealPreviewSubmitBtn"><i class="fa-solid fa-paper-plane me-1"></i>Submit</button>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php include __DIR__ . '/includes/footer.php'; ?>