Add Asset Management app

Assets from purchase invoice lines, straight-line depreciation/amortization with salvage value, disposals, and [Asset Mgmt] sources in Batch GL Entries. Green third workspace with an app access guard.
This commit is contained in:
Thanakorn
2026-09-15 09:50:55 +07:00
parent 9512d440dd
commit c957280aa5
46 changed files with 4316 additions and 131 deletions
+82 -18
View File
@@ -39,6 +39,22 @@
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#tab-payment">Payment</a>
</li>
<!-- Entries prepared in Asset Management -->
<li class="nav-item d-flex align-items-center ms-lg-3 px-2">
<span class="badge bg-success text-white"><i class="ti ti-building-warehouse me-1"></i>Asset Management</span>
</li>
<li class="nav-item">
<a class="nav-link text-success" data-bs-toggle="tab" href="#tab-asset-capitalization">Capitalization</a>
</li>
<li class="nav-item">
<a class="nav-link text-success" data-bs-toggle="tab" href="#tab-asset-depreciation">Depreciation</a>
</li>
<li class="nav-item">
<a class="nav-link text-success" data-bs-toggle="tab" href="#tab-asset-amortization">Amortization</a>
</li>
<li class="nav-item">
<a class="nav-link text-success" data-bs-toggle="tab" href="#tab-asset-disposal">Disposal</a>
</li>
</ul>
<div class="tab-content">
@@ -60,6 +76,18 @@
<div class="tab-pane fade" id="tab-payment">
<?php echo gl_tab_html('payment', 'Payment'); ?>
</div>
<div class="tab-pane fade" id="tab-asset-capitalization">
<?php echo gl_tab_html('asset_capitalization', 'Asset Capitalization', true); ?>
</div>
<div class="tab-pane fade" id="tab-asset-depreciation">
<?php echo gl_tab_html('asset_depreciation', 'Asset Depreciation', true); ?>
</div>
<div class="tab-pane fade" id="tab-asset-amortization">
<?php echo gl_tab_html('asset_amortization', 'Asset Amortization', true); ?>
</div>
<div class="tab-pane fade" id="tab-asset-disposal">
<?php echo gl_tab_html('asset_disposal', 'Asset Disposal', true); ?>
</div>
</div>
</div>
</div>
@@ -76,9 +104,17 @@
'purchase_invoice': 'purchase_invoice',
'supplier_credit_note': 'supplier_credit_note',
'receipt': 'receipt',
'payment': 'payment'
'payment': 'payment',
'asset_capitalization': 'asset_capitalization',
'asset_depreciation': 'asset_depreciation',
'asset_amortization': 'asset_amortization',
'asset_disposal': 'asset_disposal'
};
// Prepared in Asset Management: no GL formula, accounts come from the asset category.
var ASSET_TYPES = ['asset_capitalization', 'asset_depreciation', 'asset_amortization', 'asset_disposal'];
function is_asset_type(doc_type) { return ASSET_TYPES.indexOf(doc_type) !== -1; }
var _gl_loaded = {}; // doc_type → [ids] (flat, used by single-formula run)
var _gl_rows = {}; // doc_type → [rows]
var _gl_failed = {}; // doc_type → failed items from last run
@@ -91,11 +127,13 @@
if (_inited[doc_type]) return;
_inited[doc_type] = true;
load_formula_options('gl-formula-' + doc_type, doc_type, null, function(formulas) {
var $sel = $('#gl-formula-' + doc_type);
$sel.prepend('<option value="all">— All Formulas (' + formulas.length + ') —</option>');
$sel.val('all');
});
if (!is_asset_type(doc_type)) {
load_formula_options('gl-formula-' + doc_type, doc_type, null, function(formulas) {
var $sel = $('#gl-formula-' + doc_type);
$sel.prepend('<option value="all">— All Formulas (' + formulas.length + ') —</option>');
$sel.val('all');
});
}
var today = new Date();
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
@@ -117,7 +155,7 @@
// ── load documents ────────────────────────────────────────────────────────
function load_gl_tab(doc_type) {
var formula_id = $('#gl-formula-' + doc_type).val();
var formula_id = is_asset_type(doc_type) ? '0' : $('#gl-formula-' + doc_type).val();
if (!formula_id) { bootbox.alert('Please select a GL formula first.'); return; }
if (formula_id === 'all') {
@@ -256,7 +294,9 @@
var status_badge = r.gl_status == 1
? '<span class="badge bg-success-subtle text-success">Posted</span>'
: '<span class="badge bg-danger-subtle text-danger">Unposted</span>';
var mapping_badge = product_mapping_badge(r);
var mapping_badge = is_asset_type(doc_type)
? '<span class="badge bg-success-subtle text-success">Asset category</span>'
: product_mapping_badge(r);
html +=
'<tr>' +
'<td>' + escape_html(r.doc_number) + '</td>' +
@@ -302,11 +342,15 @@
purchase_invoice: 'Purchase Invoice',
supplier_credit_note: 'Supplier Credit Note',
receipt: 'Receipt',
payment: 'Payment'
payment: 'Payment',
asset_capitalization: '[Asset Mgmt] Capitalization',
asset_depreciation: '[Asset Mgmt] Depreciation',
asset_amortization: '[Asset Mgmt] Amortization',
asset_disposal: '[Asset Mgmt] Disposal'
};
function run_gl_batch(doc_type) {
var formula_id = $('#gl-formula-' + doc_type).val();
var formula_id = is_asset_type(doc_type) ? '0' : $('#gl-formula-' + doc_type).val();
if (formula_id === 'all') {
run_all_formula_batches(doc_type);
@@ -393,7 +437,7 @@
// ── core batch processor ──────────────────────────────────────────────────
function _start_gl_batch(doc_type, ids, onComplete, formula_id_override) {
var formula_id = formula_id_override || $('#gl-formula-' + doc_type).val();
var formula_id = formula_id_override || (is_asset_type(doc_type) ? 0 : $('#gl-formula-' + doc_type).val());
var $msg = $('#gl-msg-' + doc_type);
set_btn_state('#gl-btn-load-' + doc_type, false);
@@ -516,10 +560,34 @@
</html>
<?php
function gl_tab_html(string $doc_type, string $label): string {
function gl_tab_html(string $doc_type, string $label, bool $from_asset = false): string {
// Asset Management entries have no GL formula: say where they come from instead.
$source_field = $from_asset
? <<<HTML
<div class="col-auto">
<label class="form-label mb-1 small">Source</label>
<div><span class="badge bg-success text-white py-2 px-3"><i class="ti ti-building-warehouse me-1"></i>From Asset Management</span></div>
</div>
HTML
: <<<HTML
<div class="col-auto">
<label class="form-label mb-1 small">GL Formula <span class="text-danger">*</span></label>
<select id="gl-formula-{$doc_type}" class="form-select form-select-sm" style="width:220px;">
</select>
</div>
HTML;
$source_note = $from_asset
? '<div class="alert alert-success small py-2 mb-3"><i class="ti ti-info-circle me-1"></i>'
. 'Prepared in <strong>Asset Management</strong>. Accounts come from each asset category; '
. 'entries are labelled <strong>[Asset Mgmt]</strong> in the GL Journal.</div>'
: '';
$accounts_header = $from_asset ? 'Accounts' : 'Product Accounts';
return <<<HTML
<div class="card p-4">
{$source_note}
<!-- Filters + action buttons -->
<div class="row g-2 align-items-end mb-3">
<div class="col-auto">
@@ -532,11 +600,7 @@ function gl_tab_html(string $doc_type, string $label): string {
<input type="text" id="gl-date-to-{$doc_type}" class="form-control form-control-sm"
placeholder="d/m/Y" style="width:140px;">
</div>
<div class="col-auto">
<label class="form-label mb-1 small">GL Formula <span class="text-danger">*</span></label>
<select id="gl-formula-{$doc_type}" class="form-select form-select-sm" style="width:220px;">
</select>
</div>
{$source_field}
<div class="col-auto">
<button id="gl-btn-load-{$doc_type}" class="btn btn-secondary btn-sm"
onclick="load_gl_tab('{$doc_type}')">
@@ -565,7 +629,7 @@ function gl_tab_html(string $doc_type, string $label): string {
<th>Contact</th>
<th class="text-end">Amount</th>
<th>Date</th>
<th>Product Accounts</th>
<th>{$accounts_header}</th>
<th>GL Status</th>
</tr>
</thead>