Files
wms-app/app/journal/new.php
T
2026-05-20 10:17:02 +07:00

265 lines
11 KiB
PHP

<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_sidebar_ac.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row mb-5">
<div class="col-12">
<div class="d-flex align-items-center gap-3">
<a href="<?php echo $server_url?>journal/index.php" class="btn btn-light btn-sm">
<i class="ti ti-arrow-left me-1"></i>Back
</a>
<div>
<h1 class="fs-3 mb-0" id="page_title">New Manual Journal Entry</h1>
</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- Header -->
<div class="col-lg-8">
<div class="card">
<div class="card-body p-5">
<input type="hidden" id="gl_id">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Journal Date <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="journal_date" placeholder="d/m/Y" required>
</div>
<div class="col-md-4">
<label class="form-label">Period (auto)</label>
<input type="text" class="form-control bg-light" id="period_display" readonly placeholder="YYYY-MM">
</div>
<div class="col-md-4">
<label class="form-label">Reference</label>
<input type="text" class="form-control" id="reference" placeholder="e.g. MJE-001">
</div>
<div class="col-12">
<label class="form-label">Description</label>
<input type="text" class="form-control" id="description" placeholder="Journal entry description">
</div>
</div>
</div>
</div>
</div>
<!-- Balance indicator -->
<div class="col-lg-4">
<div class="card h-100">
<div class="card-body d-flex flex-column justify-content-center align-items-center gap-2 p-5">
<p class="text-muted small mb-1">Debit Total</p>
<h3 class="fw-bold mb-0 text-primary" id="bal_debit">0.00</h3>
<p class="text-muted small mb-1 mt-3">Credit Total</p>
<h3 class="fw-bold mb-0 text-danger" id="bal_credit">0.00</h3>
<hr class="w-100">
<p class="text-muted small mb-0" id="bal_status">Enter journal lines below.</p>
</div>
</div>
</div>
<!-- Journal lines -->
<div class="col-12">
<div class="card">
<div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Journal Lines</h6>
<button class="btn btn-outline-primary btn-sm" onclick="add_line()">
<i class="ti ti-plus me-1"></i>Add Line
</button>
</div>
<div class="table-responsive">
<table class="table mb-0" id="lines_table">
<thead class="table-light">
<tr>
<th style="width:220px;">Account</th>
<th>Description</th>
<th style="width:150px;" class="text-end">Debit</th>
<th style="width:150px;" class="text-end">Credit</th>
<th style="width:40px;"></th>
</tr>
</thead>
<tbody id="lines_tbody"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Submit -->
<div class="col-12">
<button class="btn btn-primary" onclick="save_journal()">
<i class="ti ti-device-floppy me-1"></i><span id="btn_save_text">Save Entry</span>
</button>
<a href="<?php echo $server_url?>journal/index.php" class="btn btn-secondary ms-2">Cancel</a>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var all_accounts = [];
var line_counter = 0;
// ── Load accounts ─────────────────────────────────────────────────────────────
ajax_request({
url: server_url + 'accounting/api/engine/account.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
all_accounts = (res.output || []).filter(function(a){ return a.is_posting == 1 && a.status == 1; });
add_line();
add_line();
load_for_edit();
}
});
// ── Flatpickr date ────────────────────────────────────────────────────────────
$(function() {
flatpickr('#journal_date', {
dateFormat: 'd/m/Y',
onChange: function(sel, str) {
if (str.length === 10) {
var parts = str.split('/');
document.getElementById('period_display').value = parts[2] + '-' + parts[1];
}
}
});
// Default to today
var t = new Date();
var dd = String(t.getDate()).padStart(2,'0');
var mm = String(t.getMonth()+1).padStart(2,'0');
var yy = t.getFullYear();
document.getElementById('journal_date').value = dd + '/' + mm + '/' + yy;
document.getElementById('period_display').value = yy + '-' + mm;
});
// ── Lines ─────────────────────────────────────────────────────────────────────
function account_opts(selected) {
var opts = '<option value="">— Account —</option>';
all_accounts.forEach(function(a) {
var sel = (selected && selected === a.account_code) ? 'selected' : '';
opts += '<option value="' + escape_html(a.account_code) + '" ' + sel + '>' +
escape_html(a.account_code + ' — ' + a.account_name) + '</option>';
});
return opts;
}
function add_line(code, desc, dr, cr) {
line_counter++;
var id = 'line_' + line_counter;
var tr = document.createElement('tr');
tr.id = id;
tr.innerHTML =
'<td><select class="form-select form-select-sm line-account" onchange="recalc()">' + account_opts(code || '') + '</select></td>' +
'<td><input type="text" class="form-control form-control-sm line-desc" value="' + escape_html(desc || '') + '" placeholder="Line description"></td>' +
'<td><input type="number" class="form-control form-control-sm text-end line-debit" min="0" step="0.01" value="' + (dr || '') + '" oninput="recalc()"></td>' +
'<td><input type="number" class="form-control form-control-sm text-end line-credit" min="0" step="0.01" value="' + (cr || '') + '" oninput="recalc()"></td>' +
'<td><button class="btn btn-sm btn-link text-danger p-0" onclick="remove_line(\'' + id + '\')"><i class="ti ti-trash"></i></button></td>';
document.getElementById('lines_tbody').appendChild(tr);
recalc();
}
function remove_line(id) {
var tbody = document.getElementById('lines_tbody');
if (tbody.rows.length <= 2) { alert('Minimum 2 lines required.'); return; }
document.getElementById(id).remove();
recalc();
}
function recalc() {
var dr = 0, cr = 0;
document.querySelectorAll('.line-debit').forEach(function(el) { dr += parseFloat(el.value) || 0; });
document.querySelectorAll('.line-credit').forEach(function(el) { cr += parseFloat(el.value) || 0; });
document.getElementById('bal_debit').textContent = dr.toFixed(2);
document.getElementById('bal_credit').textContent = cr.toFixed(2);
var diff = Math.abs(dr - cr);
var status = document.getElementById('bal_status');
if (diff < 0.005) {
status.textContent = 'Balanced';
status.className = 'text-success small mb-0 fw-semibold';
} else {
status.textContent = 'Out of balance by ' + diff.toFixed(2);
status.className = 'text-danger small mb-0';
}
}
// ── Save ──────────────────────────────────────────────────────────────────────
function save_journal() {
var jdate = document.getElementById('journal_date').value;
if (!jdate) { alert('Journal date is required.'); return; }
var lines = [];
document.querySelectorAll('#lines_tbody tr').forEach(function(tr) {
var code = tr.querySelector('.line-account').value;
var desc = tr.querySelector('.line-desc').value;
var debit = parseFloat(tr.querySelector('.line-debit').value) || 0;
var credit= parseFloat(tr.querySelector('.line-credit').value) || 0;
if (code && (debit || credit)) lines.push({ account_code: code, description: desc, debit: debit, credit: credit });
});
ajax_request({
url: server_url + 'accounting/api/engine/save_manual_journal.php',
autoPrepare: true,
checkRequired: 0,
action: 'manage',
gl_id: document.getElementById('gl_id').value || 0,
journal_date: jdate,
reference: document.getElementById('reference').value,
description: document.getElementById('description').value,
lines: JSON.stringify(lines),
onSuccess: function() {
window.location.href = server_url + 'journal/index.php';
}
});
}
// ── Edit mode ─────────────────────────────────────────────────────────────────
function load_for_edit() {
<?php if (empty($_GET['gl_id'])): ?>
return;
<?php endif; ?>
ajax_request({
url: server_url + 'accounting/api/engine/get_journal_detail.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
gl_id: <?php echo (int)$_GET['gl_id']; ?>,
onSuccess: function(res) {
var d = res.output;
var h = d.header;
document.getElementById('gl_id').value = h.id;
document.getElementById('journal_date').value = h.journal_date_fmt || '';
document.getElementById('period_display').value = h.period || '';
document.getElementById('reference').value = h.reference || '';
document.getElementById('description').value = h.description || '';
document.getElementById('page_title').textContent = 'Edit Journal Entry';
document.getElementById('btn_save_text').textContent = 'Update Entry';
// Clear default blank lines and repopulate
document.getElementById('lines_tbody').innerHTML = '';
(d.lines || []).forEach(function(l) {
add_line(l.account_code, l.description, l.debit, l.credit);
});
}
});
}
</script>
</body>
</html>