Compare commits

...
2 Commits
Author SHA1 Message Date
Thanakorn c14822add6 Merge fix/manual-journal 2026-09-18 16:33:35 +07:00
Thanakorn c60b705d98 Fix GL journal save, edit, detail view and list filters
Send journal lines, gl_id and list filters inside the ajax data payload where ajax_request reads them, select period when replacing a manual journal, and show ledger amounts to two decimals.
2026-09-18 16:19:27 +07:00
4 changed files with 48 additions and 19 deletions
+5 -4
View File
@@ -157,8 +157,10 @@
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'save', action: 'save',
open_from: from, // Inside `data`: as top-level options these were ignored, and the save
open_to: to, // only worked because autoPrepare happens to sweep the two inputs, whose
// ids match the field names.
data: { open_from: from, open_to: to },
onSuccess: function() { onSuccess: function() {
render_display(from, to); render_display(from, to);
} }
@@ -173,8 +175,7 @@
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'save', action: 'save',
open_from: '', data: { open_from: '', open_to: '' },
open_to: '',
onSuccess: function() { onSuccess: function() {
render_display('', ''); render_display('', '');
} }
+5 -1
View File
@@ -81,7 +81,11 @@ class GlManager
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
$sth = $this->pdo->prepare( $sth = $this->pdo->prepare(
"SELECT id, current_version, formula_id, history // `period` is read below as $old_period to reverse the old ETL
// totals. It was missing from this list, so it was always null and
// upsertEtl(string $period) threw a TypeError — every edit of a
// manual journal ended in HTTP 500.
"SELECT id, current_version, formula_id, history, period
FROM td_gl FROM td_gl
WHERE company_id = :cid AND id = :gl_id AND source_type = 'manual' WHERE company_id = :cid AND id = :gl_id AND source_type = 'manual'
FOR UPDATE" FOR UPDATE"
+25 -7
View File
@@ -117,16 +117,32 @@
}; };
function load_listing() { function load_listing() {
var period = document.getElementById('f_period').value; var period = document.getElementById('f_period').value; // "YYYY-MM"
var source = document.getElementById('f_source').value; var source = document.getElementById('f_source').value;
// The engine filters on a date range, not a period, so turn the chosen
// month into its first and last day.
var date_from = '', date_to = '';
if (/^\d{4}-\d{2}$/.test(period)) {
var ym = period.split('-');
var last_day = new Date(parseInt(ym[0]), parseInt(ym[1]), 0).getDate();
date_from = period + '-01';
date_to = period + '-' + ('0' + last_day).slice(-2);
}
// Payload fields must go inside `data` — ajax_request() ignores unknown
// top-level options, so as siblings of `url` these were never sent and the
// month / source filters silently did nothing.
ajax_request({ ajax_request({
url: server_url + 'accounting/api/engine/get_journal_listing.php', url: server_url + 'accounting/api/engine/get_journal_listing.php',
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'read', action: 'read',
period: period, data: {
source_type: source, source_type: source,
date_from: date_from,
date_to: date_to
},
onSuccess: function(res) { onSuccess: function(res) {
var rows = res.output || []; var rows = res.output || [];
document.getElementById('jl_badge').textContent = rows.length + ' entries'; document.getElementById('jl_badge').textContent = rows.length + ' entries';
@@ -146,8 +162,8 @@
'<td><span class="badge rounded-pill ' + cls + ' small">' + escape_html(label) + '</span></td>' + '<td><span class="badge rounded-pill ' + cls + ' small">' + escape_html(label) + '</span></td>' +
'<td class="small text-muted">' + escape_html(r.contact_name || '—') + '</td>' + '<td class="small text-muted">' + escape_html(r.contact_name || '—') + '</td>' +
'<td class="small">' + escape_html(r.period) + '</td>' + '<td class="small">' + escape_html(r.period) + '</td>' +
'<td class="text-end small">' + format_number(r.total_debit) + '</td>' + '<td class="text-end small">' + format_number(r.total_debit, 2) + '</td>' +
'<td class="text-end small">' + format_number(r.total_credit) + '</td>' + '<td class="text-end small">' + format_number(r.total_credit, 2) + '</td>' +
'<td class="small text-muted">' + escape_html(r.posted_at) + '</td>' + '<td class="small text-muted">' + escape_html(r.posted_at) + '</td>' +
'<td><a href="javascript:;" onclick="view_detail(' + r.id + ')"><i class="ti ti-eye fs-5"></i></a>' + '<td><a href="javascript:;" onclick="view_detail(' + r.id + ')"><i class="ti ti-eye fs-5"></i></a>' +
(r.source_type === 'manual' ? ' <a href="' + server_url + 'journal/new.php?gl_id=' + r.id + '" class="ms-2"><i class="ti ti-edit fs-5"></i></a>' : '') + (r.source_type === 'manual' ? ' <a href="' + server_url + 'journal/new.php?gl_id=' + r.id + '" class="ms-2"><i class="ti ti-edit fs-5"></i></a>' : '') +
@@ -169,7 +185,9 @@
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'read', action: 'read',
gl_id: gl_id, // Inside `data`, or it is never sent and the engine answers
// "gl_id is required." for every row.
data: { gl_id: gl_id },
onSuccess: function(res) { onSuccess: function(res) {
var d = res.output; var d = res.output;
var h = d.header; var h = d.header;
@@ -180,8 +198,8 @@
'<td>' + escape_html(l.account_code) + '</td>' + '<td>' + escape_html(l.account_code) + '</td>' +
'<td>' + escape_html(l.account_name) + '</td>' + '<td>' + escape_html(l.account_name) + '</td>' +
'<td class="text-muted small">' + escape_html(l.description || '—') + '</td>' + '<td class="text-muted small">' + escape_html(l.description || '—') + '</td>' +
'<td class="text-end">' + (parseFloat(l.debit) ? format_number(l.debit) : '—') + '</td>' + '<td class="text-end">' + (parseFloat(l.debit) ? format_number(l.debit, 2) : '—') + '</td>' +
'<td class="text-end">' + (parseFloat(l.credit) ? format_number(l.credit) : '—') + '</td>' + '<td class="text-end">' + (parseFloat(l.credit) ? format_number(l.credit, 2) : '—') + '</td>' +
'</tr>'; '</tr>';
}).join(''); }).join('');
+8 -2
View File
@@ -216,11 +216,17 @@
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'manage', action: 'manage',
// Payload fields must go inside `data` — ajax_request() ignores unknown
// top-level options. As siblings of `url`, `lines` was never sent (it is
// not a form field, so autoPrepare could not pick it up either) and every
// save was refused with "At least two journal lines are required."
data: {
gl_id: document.getElementById('gl_id').value || 0, gl_id: document.getElementById('gl_id').value || 0,
journal_date: jdate, journal_date: jdate,
reference: document.getElementById('reference').value, reference: document.getElementById('reference').value,
description: document.getElementById('description').value, description: document.getElementById('description').value,
lines: JSON.stringify(lines), lines: JSON.stringify(lines)
},
onSuccess: function() { onSuccess: function() {
window.location.href = server_url + 'journal/index.php'; window.location.href = server_url + 'journal/index.php';
} }
@@ -238,7 +244,7 @@
autoPrepare: true, autoPrepare: true,
checkRequired: 0, checkRequired: 0,
action: 'read', action: 'read',
gl_id: <?php echo (int)$_GET['gl_id']; ?>, data: { gl_id: <?php echo (int)($_GET['gl_id'] ?? 0); ?> },
onSuccess: function(res) { onSuccess: function(res) {
var d = res.output; var d = res.output;
var h = d.header; var h = d.header;