import updated
This commit is contained in:
parent
5ba9886549
commit
a2a8ca4262
189
import_expenses.php
Normal file
189
import_expenses.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
$pageTitle = 'Import Expenses';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$results = [];
|
||||
|
||||
// Helper to get maps for lookups
|
||||
function getLookupMaps() {
|
||||
$db = db();
|
||||
$maps = [
|
||||
'projects' => [],
|
||||
'suppliers' => [],
|
||||
'types' => []
|
||||
];
|
||||
|
||||
$s = $db->query("SELECT id, project_code FROM projects");
|
||||
while($r = $s->fetch(PDO::FETCH_ASSOC)) $maps['projects'][strtolower($r['project_code'])] = $r['id'];
|
||||
|
||||
$s = $db->query("SELECT id, name FROM suppliers");
|
||||
while($r = $s->fetch(PDO::FETCH_ASSOC)) $maps['suppliers'][strtolower($r['name'])] = $r['id'];
|
||||
|
||||
$s = $db->query("SELECT id, name FROM expense_types");
|
||||
while($r = $s->fetch(PDO::FETCH_ASSOC)) $maps['types'][strtolower($r['name'])] = $r['id'];
|
||||
|
||||
return $maps;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
$file = $_FILES['csv_file'];
|
||||
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$error = 'File upload failed.';
|
||||
} else {
|
||||
$handle = fopen($file['tmp_name'], 'r');
|
||||
$header = fgetcsv($handle);
|
||||
|
||||
$expected = ['project_code', 'supplier_name', 'expense_type', 'amount', 'date', 'notes'];
|
||||
|
||||
if (!$header || count(array_intersect($expected, $header)) < 3) {
|
||||
$error = 'Invalid CSV format. Please ensure project_code, supplier_name, and expense_type are present.';
|
||||
} else {
|
||||
$columnMap = array_flip($header);
|
||||
$maps = getLookupMaps();
|
||||
$rowCount = 0;
|
||||
$importCount = 0;
|
||||
$skippedCount = 0;
|
||||
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO expenses (tenant_id, project_id, supplier_id, expense_type_id, amount, allocation_percent, entry_date, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$rowCount++;
|
||||
$data = [];
|
||||
foreach ($expected as $col) {
|
||||
$data[$col] = isset($columnMap[$col]) && isset($row[$columnMap[$col]]) ? trim($row[$columnMap[$col]]) : '';
|
||||
}
|
||||
|
||||
$projId = $maps['projects'][strtolower($data['project_code'])] ?? null;
|
||||
$suppId = $maps['suppliers'][strtolower($data['supplier_name'])] ?? null;
|
||||
$typeId = $maps['types'][strtolower($data['expense_type'])] ?? null;
|
||||
|
||||
if (!$projId) {
|
||||
$results[] = "Row $rowCount: Skipped (Unknown Project Code: " . htmlspecialchars($data['project_code']) . ")";
|
||||
$skippedCount++;
|
||||
continue;
|
||||
}
|
||||
if (!$suppId) {
|
||||
$results[] = "Row $rowCount: Skipped (Unknown Supplier: " . htmlspecialchars($data['supplier_name']) . ")";
|
||||
$skippedCount++;
|
||||
continue;
|
||||
}
|
||||
if (!$typeId) {
|
||||
$results[] = "Row $rowCount: Skipped (Unknown Expense Type: " . htmlspecialchars($data['expense_type']) . ")";
|
||||
$skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount = floatval($data['amount']);
|
||||
$date = empty($data['date']) ? date('Y-m-d') : $data['date'];
|
||||
|
||||
$stmt->execute([
|
||||
1, // tenant_id
|
||||
$projId,
|
||||
$suppId,
|
||||
$typeId,
|
||||
$amount,
|
||||
100.00, // Default allocation_percent
|
||||
$date,
|
||||
$data['notes']
|
||||
]);
|
||||
$importCount++;
|
||||
}
|
||||
db()->commit();
|
||||
$message = "Import completed. $importCount expenses imported, $skippedCount skipped.";
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
include 'includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-receipt me-2"></i>Import Expenses</h2>
|
||||
<a href="samples/expenses_template.csv" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-download me-1"></i>Download Template
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<?= $message ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<?= $error ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<form action="import_expenses.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-4">
|
||||
<label for="csv_file" class="form-label fw-bold">Select CSV File</label>
|
||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<div class="form-text mt-2">
|
||||
Max file size: 2MB. Only .csv files are allowed.
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 py-2">
|
||||
<i class="bi bi-upload me-2"></i>Upload and Import
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0">Import Instructions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ol class="small text-muted">
|
||||
<li>Download the CSV template.</li>
|
||||
<li>Ensure <strong>project_code</strong>, <strong>supplier_name</strong>, and <strong>expense_type</strong> match existing records in the system.</li>
|
||||
<li><strong>amount</strong> should be a numeric value.</li>
|
||||
<li><strong>date</strong> should be in YYYY-MM-DD format (defaults to today if empty).</li>
|
||||
</ol>
|
||||
<div class="alert alert-warning py-2 small mb-0">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i> Rows with unknown projects, suppliers, or types will be skipped.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($results)): ?>
|
||||
<div class="card shadow-sm border-0 mt-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="mb-0">Import Logs</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-flush small">
|
||||
<?php foreach ($results as $res): ?>
|
||||
<div class="list-group-item px-0 border-0 py-1">
|
||||
<i class="bi bi-dot me-1"></i><?= $res ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php include 'includes/footer.php'; ?>
|
||||
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
$pageTitle = 'Import Clients';
|
||||
$pageTitle = 'Import Suppliers';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
@ -15,8 +15,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
$handle = fopen($file['tmp_name'], 'r');
|
||||
$header = fgetcsv($handle);
|
||||
|
||||
// Expected columns: name, email, phone, address, city, province_state, postal_code, country
|
||||
$expected = ['name', 'email', 'phone', 'address', 'city', 'province_state', 'postal_code', 'country'];
|
||||
// Expected columns: name, type, contact_info
|
||||
$expected = ['name', 'type', 'contact_info'];
|
||||
|
||||
if (!$header || count(array_intersect($expected, $header)) < 1) {
|
||||
$error = 'Invalid CSV format. Please use the provided template.';
|
||||
@ -28,7 +28,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
|
||||
db()->beginTransaction();
|
||||
try {
|
||||
$stmt = db()->prepare("INSERT INTO clients (tenant_id, name, email, phone, address, city, province_state, postal_code, country) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt = db()->prepare("INSERT INTO suppliers (tenant_id, name, type, contact_info) VALUES (?, ?, ?, ?)");
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$rowCount++;
|
||||
@ -42,27 +42,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
|
||||
$results[] = "Row $rowCount: Skipped (Missing Name)";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Simple validation: email
|
||||
if (!empty($data['email']) && !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
|
||||
$results[] = "Row $rowCount: Warning (Invalid Email: " . htmlspecialchars($data['email']) . ")";
|
||||
|
||||
// Default type if invalid
|
||||
if (!in_array($data['type'], ['supplier', 'contractor'])) {
|
||||
$data['type'] = 'supplier';
|
||||
}
|
||||
|
||||
$stmt->execute([
|
||||
1, // Hardcoded tenant_id for now as per app convention
|
||||
1, // Hardcoded tenant_id
|
||||
$data['name'],
|
||||
$data['email'],
|
||||
$data['phone'],
|
||||
$data['address'],
|
||||
$data['city'],
|
||||
$data['province_state'],
|
||||
$data['postal_code'],
|
||||
$data['country']
|
||||
$data['type'],
|
||||
$data['contact_info']
|
||||
]);
|
||||
$importCount++;
|
||||
}
|
||||
db()->commit();
|
||||
$message = "Import completed successfully. $importCount clients imported, $skippedCount skipped.";
|
||||
$message = "Import completed successfully. $importCount suppliers imported, $skippedCount skipped.";
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
@ -77,8 +72,8 @@ include 'includes/header.php';
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-file-earmark-arrow-up me-2"></i>Import Clients</h2>
|
||||
<a href="samples/clients_template.csv" class="btn btn-outline-primary btn-sm">
|
||||
<h2><i class="bi bi-truck me-2"></i>Import Suppliers</h2>
|
||||
<a href="samples/suppliers_template.csv" class="btn btn-outline-primary btn-sm">
|
||||
<i class="bi bi-download me-1"></i>Download Template
|
||||
</a>
|
||||
</div>
|
||||
@ -101,7 +96,7 @@ include 'includes/header.php';
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<form action="import_clients.php" method="POST" enctype="multipart/form-data">
|
||||
<form action="import_suppliers.php" method="POST" enctype="multipart/form-data">
|
||||
<div class="mb-4">
|
||||
<label for="csv_file" class="form-label fw-bold">Select CSV File</label>
|
||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
@ -124,13 +119,12 @@ include 'includes/header.php';
|
||||
<div class="card-body">
|
||||
<ol class="small text-muted">
|
||||
<li>Download the CSV template using the button above.</li>
|
||||
<li>Fill in your client data, ensuring the <strong>name</strong> field is not empty.</li>
|
||||
<li>Save the file as a CSV (Comma Separated Values).</li>
|
||||
<li>Upload the file using the form on the left.</li>
|
||||
<li>Review any warnings or errors that appear after processing.</li>
|
||||
<li>Fill in your supplier data. The <strong>name</strong> field is required.</li>
|
||||
<li>The <strong>type</strong> field should be either 'supplier' or 'contractor'.</li>
|
||||
<li>Save the file as a CSV and upload it.</li>
|
||||
</ol>
|
||||
<div class="alert alert-info py-2 small mb-0">
|
||||
<i class="bi bi-info-circle me-1"></i> Existing clients with the same name will be added as new entries.
|
||||
<i class="bi bi-info-circle me-1"></i> Existing suppliers with the same name will be added as new entries.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,14 +67,15 @@ $currentPage = basename($_SERVER['PHP_SELF']);
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle <?= in_array($currentPage, ['settings.php', 'system_preferences.php', 'import_clients.php', 'import_labour.php']) ? 'active' : '' ?>" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<a class="nav-link dropdown-toggle <?= in_array($currentPage, ['settings.php', 'system_preferences.php', 'import_suppliers.php', 'import_expenses.php', 'import_labour.php']) ? 'active' : '' ?>" href="#" role="button" data-bs-toggle="dropdown">
|
||||
Settings
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="settings.php">Datasets</a></li>
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="system_preferences.php">System Preferences</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="import_clients.php">Import Clients</a></li>
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="import_suppliers.php">Import Suppliers</a></li>
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="import_expenses.php">Import Expenses</a></li>
|
||||
<li><a class="dropdown-menu-item dropdown-item" href="import_labour.php">Import Labour</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
name,email,phone,address,city,province_state,postal_code,country
|
||||
Acme Corp,contact@acme.com,555-0123,123 Industrial Way,Tech City,ON,M5V 2N2,Canada
|
||||
Global Tech,info@globaltech.io,555-9876,456 Innovation Blvd,Futureville,QC,H3B 1A1,Canada
|
||||
|
4
samples/expenses_template.csv
Normal file
4
samples/expenses_template.csv
Normal file
@ -0,0 +1,4 @@
|
||||
project_code,supplier_name,expense_type,amount,date,notes
|
||||
P001,ACME Corp,Materials,1250.00,2026-02-10,Lumber for site A
|
||||
P002,Build-It Co,Subcontractors,5000.00,2026-02-12,Foundation work
|
||||
P001,Global Telecom,Overhead,150.00,2026-02-15,Monthly internet
|
||||
|
4
samples/suppliers_template.csv
Normal file
4
samples/suppliers_template.csv
Normal file
@ -0,0 +1,4 @@
|
||||
name,type,contact_info
|
||||
ACME Corp,supplier,admin@acme.com
|
||||
Build-It Co,contractor,contact@buildit.com
|
||||
Global Telecom,supplier,info@globaltelecom.com
|
||||
|
@ -240,8 +240,12 @@ include __DIR__ . '/includes/header.php';
|
||||
<div class="card-body">
|
||||
<p class="small text-muted mb-4">Import legacy data or bulk records from other systems using CSV templates.</p>
|
||||
<div class="d-grid gap-3">
|
||||
<a href="import_clients.php" class="btn btn-outline-secondary d-flex justify-content-between align-items-center py-2">
|
||||
<span><i class="bi bi-people me-2"></i>Import Clients</span>
|
||||
<a href="import_suppliers.php" class="btn btn-outline-secondary d-flex justify-content-between align-items-center py-2">
|
||||
<span><i class="bi bi-truck me-2"></i>Import Suppliers</span>
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<a href="import_expenses.php" class="btn btn-outline-secondary d-flex justify-content-between align-items-center py-2">
|
||||
<span><i class="bi bi-receipt me-2"></i>Import Expenses</span>
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
<a href="import_labour.php" class="btn btn-outline-secondary d-flex justify-content-between align-items-center py-2">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user