317 lines
16 KiB
PHP
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>
|