prepare("SELECT id FROM company_settings WHERE setting_key = ?"); $stmt->execute([$key]); if ($stmt->fetch()) { $stmt = $pdo->prepare("UPDATE company_settings SET setting_value = ?, updated_by = ?, updated_at = NOW() WHERE setting_key = ?"); $stmt->execute([$value, $_SESSION['admin_id'], $key]); } else { $stmt = $pdo->prepare("INSERT INTO company_settings (setting_key, setting_value, updated_by, updated_at) VALUES (?, ?, ?, NOW())"); $stmt->execute([$key, $value, $_SESSION['admin_id']]); } } logActivity($_SESSION['admin_id'], 'update_pricing', 'Updated base pricing configuration', 'settings'); $success = 'Pricing configuration saved successfully!'; } catch (Exception $e) { error_log("Pricing save error: " . $e->getMessage()); $error = 'Error saving pricing: ' . $e->getMessage(); } } // Load current pricing values $pricing = []; try { $pdo = getDBConnection(); $stmt = $pdo->query("SELECT setting_key, setting_value, updated_at FROM company_settings WHERE setting_key LIKE 'pricing_%'"); while ($row = $stmt->fetch()) { $pricing[$row['setting_key']] = $row['setting_value']; $pricing[$row['setting_key'] . '_updated'] = $row['updated_at']; } } catch (Exception $e) { error_log("Pricing load error: " . $e->getMessage()); } // Helper to get pricing value function pv($key, $default = '') { global $pricing; return htmlspecialchars($pricing[$key] ?? $default); } // Get last updated time $last_updated = null; foreach ($pricing as $k => $v) { if (str_ends_with($k, '_updated') && $v) { if (!$last_updated || $v > $last_updated) { $last_updated = $v; } } } // === CSV Export (must run before any HTML output) === if ($tab === 'invoices' && isset($_GET['inv_export']) && $_GET['inv_export'] === 'csv') { $csvStatus=$_GET['inv_status']??''; $csvClient=$_GET['inv_client']??''; $csvDateFrom=$_GET['inv_from']??''; $csvDateTo=$_GET['inv_to']??''; $csvSearch=trim($_GET['inv_search']??''); $csvCurrency=$_GET['inv_currency']??''; $csvDueFrom=$_GET['inv_due_from']??''; $csvDueTo=$_GET['inv_due_to']??''; $csvSort=$_GET['inv_sort']??'invoice_date'; $csvDir=($_GET['inv_dir']??'desc')==='asc'?'ASC':'DESC'; $csvAllowed=['invoice_number','invoice_date','due_date','total_amount','status','project_code','client_name']; if (!in_array($csvSort,$csvAllowed)) $csvSort='invoice_date'; $csvWhere=["1=1"]; $csvParams=[]; if($csvStatus){$csvWhere[]="i.status=?";$csvParams[]=$csvStatus;} if($csvClient){$csvWhere[]="i.client_id=?";$csvParams[]=(int)$csvClient;} if($csvCurrency){$csvWhere[]="i.currency=?";$csvParams[]=$csvCurrency;} if($csvDateFrom){$csvWhere[]="i.invoice_date>=?";$csvParams[]=$csvDateFrom;} if($csvDateTo){$csvWhere[]="i.invoice_date<=?";$csvParams[]=$csvDateTo;} if($csvDueFrom){$csvWhere[]="i.due_date>=?";$csvParams[]=$csvDueFrom;} if($csvDueTo){$csvWhere[]="i.due_date<=?";$csvParams[]=$csvDueTo;} if($csvSearch){$csvWhere[]="(i.invoice_number LIKE ? OR p.project_name LIKE ? OR i.project_code LIKE ? OR c.company_name LIKE ?)";$s="%$csvSearch%";$csvParams=array_merge($csvParams,[$s,$s,$s,$s]);} $csvWc=implode(' AND ',$csvWhere); $stmt=$pdo->prepare("SELECT i.*,p.project_name,p.industry,c.company_name as client_company,c.contact_person FROM invoices i INNER JOIN projects p ON i.project_id=p.id INNER JOIN clients c ON i.client_id=c.id WHERE $csvWc ORDER BY $csvSort $csvDir"); $stmt->execute($csvParams); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="invoices_'.date('Y-m-d_His').'.csv"'); $out=fopen('php://output','w'); fprintf($out,chr(0xEF).chr(0xBB).chr(0xBF)); fputcsv($out,['Invoice #','Client','Contact','Project','Project ID','Industry','Currency','Completes','Avg LOI','Rate/Sample','Total Amount','Invoice Date','Due Date','Paid Date','Status','Payment Ref','Remarks']); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $r){ fputcsv($out,[$r['invoice_number'],$r['client_company'],$r['contact_person'],$r['project_name'],$r['project_code'],$r['industry'],$r['currency'],$r['valid_completes'],$r['avg_loi_minutes'],$r['rate_per_sample'],$r['total_amount'],$r['invoice_date'],$r['due_date'],$r['paid_date']??'',$r['status'],$r['payment_reference']??'',$r['remarks']??'']); } fclose($out);exit; } include 'includes/header.php'; ?>

Finance

Manage pricing, invoices & financial settings

💰 Pricing 📄 Invoices
🇮🇳
Base Currency
INR (₹)
Indian Rupee — all pricing in INR
🇺🇸
USD → INR
Loading...
Fetching live rate
🇪🇺
EUR → INR
Loading...
Fetching live rate

🏷 Base Rates Per Complete

Set the base cost per completed survey for an LOI of up to 5 minutes. These are the starting rates before any LOI increments are applied.

Cost charged to client per complete (LOI ≤ 5 min)
Incentive paid to panelist per complete (LOI ≤ 5 min)

⏱ LOI Increments Per Additional Minute

For surveys longer than 5 minutes, this amount is added per additional minute beyond the base 5 minutes.
Example: A 15-min survey adds 10 extra minutes × increment rate.

/min
Added to sample cost for each minute beyond 5 min LOI
/min
Added to incentive for each minute beyond 5 min LOI

💱 Currency Conversion Factors

These factors adjust the INR total before converting to foreign currency. The INR total (Sample Cost + Incentive) is divided by (100% + Factor%), then converted at the live exchange rate.
Example: Factor 10% → INR Total ÷ 1.10 = Adjusted INR → convert to USD/EUR.

%
Discount factor applied when pricing in USD
%
Discount factor applied when pricing in EUR
Last saved:

🔎 Quick Price Calculator

Test your pricing by entering an LOI. Uses the saved rates above and live exchange rates.

INR Price
USD Price
EUR Price
query("SELECT p.id, p.project_id as project_code, p.client_id FROM projects p LEFT JOIN invoices i ON i.project_id=p.id WHERE p.status='Closed' AND i.id IS NULL"); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $m) { if (function_exists('generateProjectInvoice')) generateProjectInvoice($pdo, $m['id'], $m['project_code'], $m['client_id']); } } catch (Exception $e) {} // Update statuses if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo); // Handle invoice POST actions if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $invAction = $_POST['action']; $invId = intval($_POST['invoice_id'] ?? 0); if ($invAction === 'mark_paid' && $invId) { $pdo->prepare("UPDATE invoices SET status='paid', paid_date=?, payment_reference=?, remarks=?, updated_at=NOW() WHERE id=?")->execute([$_POST['paid_date'] ?? date('Y-m-d'), trim($_POST['payment_reference'] ?? ''), trim($_POST['remarks'] ?? ''), $invId]); $success = 'Invoice marked as paid.'; logActivity($_SESSION['admin_id'], 'update', "Marked invoice #$invId as paid", 'invoice', $invId); } if ($invAction === 'save_remarks' && $invId) { $pdo->prepare("UPDATE invoices SET remarks=?, updated_at=NOW() WHERE id=?")->execute([trim($_POST['remarks'] ?? ''), $invId]); $success = 'Remarks saved.'; } if ($invAction === 'revert_unpaid' && $invId) { $pdo->prepare("UPDATE invoices SET status='invoiced', paid_date=NULL, payment_reference=NULL, updated_at=NOW() WHERE id=?")->execute([$invId]); if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo); $success = 'Invoice reverted to unpaid.'; } } // Ban enforcement try { $bannedClients = $pdo->query("SELECT DISTINCT client_id FROM invoices WHERE status != 'paid' AND due_date < DATE_SUB(CURDATE(), INTERVAL 90 DAY)")->fetchAll(PDO::FETCH_COLUMN); if (!empty($bannedClients)) { $ph = implode(',', array_fill(0, count($bannedClients), '?')); $pdo->prepare("UPDATE projects SET status='On hold' WHERE client_id IN ($ph) AND status='Live'")->execute($bannedClients); } } catch (Exception $e) {} // Invoice filters $fStatus=$_GET['inv_status']??''; $fClient=$_GET['inv_client']??''; $fDateFrom=$_GET['inv_from']??''; $fDateTo=$_GET['inv_to']??''; $fSearch=trim($_GET['inv_search']??''); $fCurrency=$_GET['inv_currency']??''; $fDueFrom=$_GET['inv_due_from']??''; $fDueTo=$_GET['inv_due_to']??''; $invSort=$_GET['inv_sort']??'invoice_date'; $invDir=($_GET['inv_dir']??'desc')==='asc'?'ASC':'DESC'; $invAllowed=['invoice_number','invoice_date','due_date','total_amount','status','project_code','client_name']; if (!in_array($invSort,$invAllowed)) $invSort='invoice_date'; $invWhere=["1=1"]; $invParams=[]; if($fStatus){$invWhere[]="i.status=?";$invParams[]=$fStatus;} if($fClient){$invWhere[]="i.client_id=?";$invParams[]=(int)$fClient;} if($fCurrency){$invWhere[]="i.currency=?";$invParams[]=$fCurrency;} if($fDateFrom){$invWhere[]="i.invoice_date>=?";$invParams[]=$fDateFrom;} if($fDateTo){$invWhere[]="i.invoice_date<=?";$invParams[]=$fDateTo;} if($fDueFrom){$invWhere[]="i.due_date>=?";$invParams[]=$fDueFrom;} if($fDueTo){$invWhere[]="i.due_date<=?";$invParams[]=$fDueTo;} if($fSearch){$invWhere[]="(i.invoice_number LIKE ? OR p.project_name LIKE ? OR i.project_code LIKE ? OR c.company_name LIKE ?)";$s="%$fSearch%";$invParams=array_merge($invParams,[$s,$s,$s,$s]);} $invWc=implode(' AND ',$invWhere); $stmt=$pdo->prepare("SELECT i.*,p.project_name,p.industry,c.company_name as client_company,c.contact_person FROM invoices i INNER JOIN projects p ON i.project_id=p.id INNER JOIN clients c ON i.client_id=c.id WHERE $invWc ORDER BY $invSort $invDir"); $stmt->execute($invParams); $allInvoices=$stmt->fetchAll(PDO::FETCH_ASSOC); $invSumAll=$invSumPaid=$invSumOut=0; $invSc=['invoiced'=>0,'paid'=>0,'due'=>0,'overdue'=>0,'critical'=>0,'banned'=>0]; foreach($allInvoices as $inv){ $invSumAll+=$inv['total_amount']; if($inv['status']==='paid')$invSumPaid+=$inv['total_amount'];else $invSumOut+=$inv['total_amount']; $invSc[$inv['status']]=($invSc[$inv['status']]??0)+1; } $allClients=$pdo->query("SELECT id,company_name FROM clients ORDER BY company_name")->fetchAll(PDO::FETCH_ASSOC); ?>
Total
Paid
Outstanding
📥 Download Invoices
All 'Invoiced','paid'=>'Paid','due'=>'Due','overdue'=>'Overdue','critical'=>'Critical','banned'=>'Banned'] as $sk=>$sl):?> 0):?>
Clear
📄

No invoices found. Invoices are auto-generated when projects are closed.

'','inv_dir'=>'']),['tab'=>'invoices']));?>
Invoice # Client Project Curr N Total Invoiced Due Status Actions
📄

* All amounts are inclusive of applicable taxes