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
+174
View File
@@ -0,0 +1,174 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_sidebar_asset.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<!-- Page header -->
<div class="row">
<div class="col-12">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 gap-3">
<div>
<h1 class="fs-3 mb-1">Asset Register</h1>
<p class="mb-0">Every asset with its cost, accumulated depreciation and book value</p>
</div>
<div class="d-flex gap-2">
<a href="<?php echo $server_url?>asset/purchase_invoices.php" class="btn btn-outline-primary">
<i class="ti ti-file-invoice me-1"></i>From Purchase Invoice
</a>
<a href="<?php echo $server_url?>asset/manage_asset.php" class="btn btn-primary">
<i class="ti ti-plus me-1"></i>New Asset
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card overflow-hidden">
<div class="card-body p-6">
<div class="mb-5 d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-3">
<ul class="nav nav-pills small" id="status_filter">
<li class="nav-item"><a class="nav-link active" href="javascript:;" data-status="">All <span class="count"></span></a></li>
<li class="nav-item"><a class="nav-link" href="javascript:;" data-status="0">Draft <span class="count"></span></a></li>
<li class="nav-item"><a class="nav-link" href="javascript:;" data-status="1">Active <span class="count"></span></a></li>
<li class="nav-item"><a class="nav-link" href="javascript:;" data-status="2">Fully depreciated <span class="count"></span></a></li>
<li class="nav-item"><a class="nav-link" href="javascript:;" data-status="3">Disposed <span class="count"></span></a></li>
<li class="nav-item"><a class="nav-link" href="javascript:;" data-status="4">Void <span class="count"></span></a></li>
</ul>
<div class="position-relative" style="min-width:240px;">
<input type="search" id="search_asset" class="form-control ps-9" placeholder="Search number, name, category…">
<span class="position-absolute top-25 ms-4">
<i class="ti ti-search text-muted"></i>
</span>
</div>
</div>
<div class="table-responsive">
<table class="table mb-0 text-nowrap table-hover table-centered" id="asset_table">
<thead class="table-primary border-light">
<tr>
<th>Asset No.</th>
<th>Name</th>
<th>Category</th>
<th>In Service</th>
<th class="text-end">Cost</th>
<th class="text-end">Accumulated</th>
<th class="text-end">Book Value</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script src="<?php echo $server_url?>assets/js/asset.js"></script>
<script>
var all_assets = [];
var search_term = '';
var status_filter = new URLSearchParams(window.location.search).get('status') || '';
function retrieve_assets() {
return ajax_request({
url: server_url + 'asset/api/engine/asset.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
all_assets = res.output || [];
update_counts();
change_page(1);
}
});
}
function update_counts() {
$('#status_filter a').each(function() {
var s = String($(this).data('status'));
var n = s === '' ? all_assets.length : all_assets.filter(function(a) { return String(a.status) === s; }).length;
$(this).find('.count').text('(' + n + ')');
$(this).toggleClass('active', s === status_filter);
});
}
function filtered_assets() {
var kw = search_term.toLowerCase();
return all_assets.filter(function(a) {
if (status_filter !== '' && String(a.status) !== status_filter) return false;
if (!kw) return true;
return [a.asset_number, a.asset_name, a.category_code, a.category_name, a.invoice_number]
.some(function(v) { return String(v || '').toLowerCase().indexOf(kw) !== -1; });
});
}
function change_page(page_num) {
var rows = filtered_assets();
var limit = parseInt(prop_limit, 10);
var offset = (page_num - 1) * limit;
$('table#asset_table tfoot').html(generate_pagination('asset_table', rows.length));
if (!rows.length) {
$('table#asset_table tbody').html('<tr><td colspan="9" class="text-center py-5 text-muted">No assets found.</td></tr>');
return;
}
var body = '';
rows.slice(offset, offset + limit).forEach(function(a) {
var source = a.invoice_number
? '<div class="small text-muted"><i class="ti ti-file-invoice me-1"></i>' + escape_html(a.invoice_number) + '</div>'
: '';
body += `<tr>
<td class="py-3 fw-semibold">${escape_html(a.asset_number)}</td>
<td class="py-3">${escape_html(a.asset_name)}${source}</td>
<td class="py-3">${escape_html(a.category_code)} <span class="text-muted">${escape_html(a.category_name)}</span></td>
<td class="py-3">${format_date(a.in_service_date)}</td>
<td class="py-3 text-end">${format_number(a.cost, 2)}</td>
<td class="py-3 text-end">${format_number(a.accumulated, 2)}</td>
<td class="py-3 text-end">${format_number(a.book_value, 2)}</td>
<td class="py-3">${asset_status_badge(a.status, a.asset_class)}</td>
<td class="py-3"><a href="${server_url}asset/manage_asset.php?id=${a.id}"><i class="ti ti-eye fs-5"></i></a></td>
</tr>`;
});
$('table#asset_table tbody').html(body);
}
$('#status_filter').on('click', 'a', function() {
status_filter = String($(this).data('status'));
update_counts();
change_page(1);
});
$('#search_asset').on('input', debounce(function() {
search_term = $(this).val().trim();
change_page(1);
}, 200));
$(async function() {
try { await retrieve_assets(); } catch (e) { console.log(e); }
});
</script>
</body>
</html>