38960-vm/includes/pages/inventory_transactions.php
2026-03-21 17:39:37 +00:00

317 lines
16 KiB
PHP

<?php
// Handle Create Transaction
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action']) && $_POST['action'] === 'add_transaction') {
$type = $_POST['type'] ?? 'in';
$item_id = $_POST['item_id'] ?? 0;
$quantity = (int)($_POST['quantity'] ?? 0);
$notes = $_POST['notes'] ?? '';
$reference_type = $_POST['reference_type'] ?? 'manual_adjustment';
$reference_id = 0; // Or pass from somewhere
if (!$item_id || $quantity <= 0) {
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('fill_all_required_fields') . '</div>';
} else {
try {
$db->beginTransaction();
if ($type === 'in') {
// Create New Batch
$batch_number = $_POST['batch_number'] ?? date('YmdHis');
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$cost_price = $_POST['cost_price'] ?? 0;
$supplier_id = !empty($_POST['supplier_id']) ? $_POST['supplier_id'] : null;
$stmt = $db->prepare("INSERT INTO inventory_batches (item_id, batch_number, expiry_date, quantity, cost_price, supplier_id, received_date) VALUES (?, ?, ?, ?, ?, ?, NOW())");
$stmt->execute([$item_id, $batch_number, $expiry_date, $quantity, $cost_price, $supplier_id]);
$batch_id = $db->lastInsertId();
// Create Transaction Record
$stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes) VALUES (?, ?, 'in', ?, ?, ?, ?, ?)");
$stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes]);
} elseif ($type === 'out') {
// Deduct from Batch
$batch_id = $_POST['batch_id'] ?? 0;
// Check availability
$stmt = $db->prepare("SELECT quantity FROM inventory_batches WHERE id = ? FOR UPDATE");
$stmt->execute([$batch_id]);
$current_qty = $stmt->fetchColumn();
if ($current_qty < $quantity) {
throw new Exception(__('insufficient_stock_in_batch'));
}
// Update Batch
$stmt = $db->prepare("UPDATE inventory_batches SET quantity = quantity - ? WHERE id = ?");
$stmt->execute([$quantity, $batch_id]);
// Create Transaction Record
$stmt = $db->prepare("INSERT INTO inventory_transactions (item_id, batch_id, transaction_type, quantity, reference_type, reference_id, user_id, notes) VALUES (?, ?, 'out', ?, ?, ?, ?, ?)");
$stmt->execute([$item_id, $batch_id, $quantity, $reference_type, $reference_id, $_SESSION['user_id'], $notes]);
}
$db->commit();
$_SESSION['flash_message'] = '<div class="alert alert-success">' . __('transaction_recorded_successfully') . '</div>';
} catch (Exception $e) {
$db->rollBack();
$_SESSION['flash_message'] = '<div class="alert alert-danger">' . __('error_recording_transaction') . ': ' . $e->getMessage() . '</div>';
}
}
header("Location: inventory_transactions.php");
exit;
}
}
// Fetch Transactions
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$per_page = 20;
$offset = ($page - 1) * $per_page;
$stmt = $db->query("SELECT COUNT(*) FROM inventory_transactions");
$total_records = $stmt->fetchColumn();
$total_pages = ceil($total_records / $per_page);
$transactions = $db->query("
SELECT t.*, i.name_en as item_name, i.sku, b.batch_number, u.name as user_name
FROM inventory_transactions t
JOIN inventory_items i ON t.item_id = i.id
LEFT JOIN inventory_batches b ON t.batch_id = b.id
LEFT JOIN users u ON t.user_id = u.id
ORDER BY t.transaction_date DESC
LIMIT $per_page OFFSET $offset
")->fetchAll(PDO::FETCH_ASSOC);
// Fetch Items for Dropdown
$items = $db->query("SELECT id, name_en, sku FROM inventory_items WHERE is_active = 1 ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
// Fetch Suppliers
$suppliers = $db->query("SELECT id, name_en FROM suppliers ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="mb-0 fw-bold text-dark"><?php echo __('inventory_transactions'); ?></h2>
<button type="button" class="btn btn-primary" onclick="openNewTransactionModal()">
<i class="bi bi-plus-lg me-1"></i> <?php echo __('new_transaction'); ?>
</button>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th><?php echo __('date'); ?></th>
<th><?php echo __('type'); ?></th>
<th><?php echo __('item'); ?></th>
<th><?php echo __('batch'); ?></th>
<th><?php echo __('quantity'); ?></th>
<th><?php echo __('user'); ?></th>
<th><?php echo __('notes'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($transactions)): ?>
<tr>
<td colspan="7" class="text-center py-4 text-muted"><?php echo __('no_transactions_found'); ?></td>
</tr>
<?php else: ?>
<?php foreach ($transactions as $t): ?>
<tr>
<td><?php echo date('Y-m-d H:i', strtotime($t['transaction_date'])); ?></td>
<td>
<?php if ($t['transaction_type'] === 'in'): ?>
<span class="badge bg-success"><i class="bi bi-arrow-down"></i> <?php echo __('in'); ?></span>
<?php elseif ($t['transaction_type'] === 'out'): ?>
<span class="badge bg-danger"><i class="bi bi-arrow-up"></i> <?php echo __('out'); ?></span>
<?php else: ?>
<span class="badge bg-warning text-dark"><?php echo __('adjustment'); ?></span>
<?php endif; ?>
</td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($t['item_name']); ?></div>
<small class="text-muted"><?php echo htmlspecialchars($t['sku']); ?></small>
</td>
<td><?php echo htmlspecialchars($t['batch_number'] ?? '-'); ?></td>
<td class="fw-bold"><?php echo $t['quantity']; ?></td>
<td><?php echo htmlspecialchars($t['user_name'] ?? 'System'); ?></td>
<td><?php echo htmlspecialchars($t['notes'] ?? ''); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<nav class="mt-4">
<ul class="pagination justify-content-center">
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<li class="page-item <?php echo $i === $page ? 'active' : ''; ?>">
<a class="page-link" href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</div>
</div>
<!-- New Transaction Modal -->
<div class="modal fade" id="transactionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="POST">
<div class="modal-header">
<h5 class="modal-title"><?php echo __('new_transaction'); ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="action" value="add_transaction">
<div class="mb-3">
<label class="form-label"><?php echo __('transaction_type'); ?></label>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="type" id="type_in" value="in" checked onchange="toggleType()">
<label class="btn btn-outline-success" for="type_in"><?php echo __('stock_in_purchase'); ?></label>
<input type="radio" class="btn-check" name="type" id="type_out" value="out" onchange="toggleType()">
<label class="btn btn-outline-danger" for="type_out"><?php echo __('stock_out_consumption'); ?></label>
</div>
</div>
<div class="mb-3">
<label class="form-label"><?php echo __('item'); ?> <span class="text-danger">*</span></label>
<select name="item_id" id="item_select" class="form-select" required onchange="fetchBatches()">
<option value=""><?php echo __('select_item'); ?></option>
<?php foreach ($items as $item): ?>
<option value="<?php echo $item['id']; ?>"><?php echo htmlspecialchars($item['name_en'] . ' (' . ($item['sku'] ?? '-') . ')'); ?></option>
<?php endforeach; ?>
</select>
</div>
<!-- Fields for IN -->
<div id="in_fields">
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label"><?php echo __('batch_number'); ?></label>
<input type="text" name="batch_number" class="form-control" placeholder="Leave empty for auto-generated">
</div>
<div class="col-md-6">
<label class="form-label"><?php echo __('expiry_date'); ?></label>
<input type="date" name="expiry_date" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label"><?php echo __('cost_price'); ?></label>
<input type="number" step="0.01" name="cost_price" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label"><?php echo __('supplier'); ?></label>
<select name="supplier_id" class="form-select">
<option value=""><?php echo __('select_supplier'); ?></option>
<?php foreach ($suppliers as $sup): ?>
<option value="<?php echo $sup['id']; ?>"><?php echo htmlspecialchars($sup['name_en']); ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<!-- Fields for OUT -->
<div id="out_fields" style="display: none;">
<div class="mb-3">
<label class="form-label"><?php echo __('select_batch_to_deduct_from'); ?> <span class="text-danger">*</span></label>
<select name="batch_id" id="batch_select" class="form-select">
<option value=""><?php echo __('select_item_first'); ?></option>
</select>
<div class="form-text text-muted" id="batch_info"></div>
</div>
</div>
<div class="mb-3">
<label class="form-label"><?php echo __('quantity'); ?> <span class="text-danger">*</span></label>
<input type="number" name="quantity" class="form-control" min="1" required>
</div>
<div class="mb-3">
<label class="form-label"><?php echo __('notes'); ?></label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
<button type="submit" class="btn btn-primary"><?php echo __('save_transaction'); ?></button>
</div>
</form>
</div>
</div>
</div>
<script>
function openNewTransactionModal() {
var myModal = new bootstrap.Modal(document.getElementById('transactionModal'));
myModal.show();
}
function toggleType() {
const isOut = document.getElementById('type_out').checked;
document.getElementById('in_fields').style.display = isOut ? 'none' : 'block';
document.getElementById('out_fields').style.display = isOut ? 'block' : 'none';
// Toggle required attribute for batch_select
document.getElementById('batch_select').required = isOut;
// Clear batch selection if switching
if (isOut) {
fetchBatches();
}
}
function fetchBatches() {
const itemId = document.getElementById('item_select').value;
const batchSelect = document.getElementById('batch_select');
const isOut = document.getElementById('type_out').checked;
if (!isOut || !itemId) return;
batchSelect.innerHTML = '<option value="">Loading...</option>';
fetch('api/inventory.php?action=get_batches&item_id=' + itemId)
.then(response => response.json())
.then(data => {
batchSelect.innerHTML = '<option value="">Select Batch</option>';
if (data.length === 0) {
batchSelect.innerHTML = '<option value="">No stock available</option>';
}
data.forEach(batch => {
const option = document.createElement('option');
option.value = batch.id;
option.text = `Batch: ${batch.batch_number} | Exp: ${batch.expiry_date || 'N/A'} | Qty: ${batch.quantity}`;
batchSelect.add(option);
});
})
.catch(err => {
console.error('Error fetching batches:', err);
batchSelect.innerHTML = '<option value="">Error loading batches</option>';
});
}
// Initial setup
document.addEventListener('DOMContentLoaded', function() {
// Initialize Select2 if available
if (typeof $().select2 === 'function') {
$('#item_select').select2({
dropdownParent: $('#transactionModal'),
theme: 'bootstrap-5'
});
$('#item_select').on('select2:select', function (e) {
fetchBatches();
});
}
});
</script>