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
+87 -69
View File
@@ -29,34 +29,58 @@ $topbar_avatar_src = $topbar_picture
: htmlspecialchars($server_url . 'assets/images/avatar-1.jpg', ENT_QUOTES, 'UTF-8');
// ── App switcher ──────────────────────────────────────────────────────────────
require_once __DIR__ . '/assets/utils/app_registry.php';
$topbar_app_access = $_SESSION['login_app_access'] ?? 'wms';
// Detect which app this page belongs to.
// Master-data pages (/accounting/, /inventory/, /contact/, /setting/) are neutral — don't change the session.
$_wms_paths = ['/dashboard/', '/ics/', '/order/', '/po/', '/reports/'];
$_ac_paths = ['/ac_dashboard/', '/revenue/', '/expense/', '/finance/', '/journal/'];
// '/asset_dashboard/' and '/ac_dashboard/' do not match '/dashboard/' (no slash before "dashboard").
$_app_paths = [
'wms' => ['/dashboard/', '/ics/', '/order/', '/po/', '/reports/'],
'accounting' => ['/ac_dashboard/', '/revenue/', '/expense/', '/finance/', '/journal/'],
'asset' => ['/asset_dashboard/', '/asset/'],
];
$topbar_current_app = null; // null = neutral page (master data / setting)
foreach ($_wms_paths as $_p) {
if (str_contains($_SERVER['PHP_SELF'], $_p)) { $topbar_current_app = 'wms'; break; }
}
if (!$topbar_current_app) {
foreach ($_ac_paths as $_p) {
if (str_contains($_SERVER['PHP_SELF'], $_p)) { $topbar_current_app = 'accounting'; break; }
foreach ($_app_paths as $_app_key => $_paths) {
foreach ($_paths as $_p) {
if (str_contains($_SERVER['PHP_SELF'], $_p)) { $topbar_current_app = $_app_key; break 2; }
}
}
// Asset Management pages are only for users given that app.
if ($topbar_current_app === 'asset' && !app_can_access($topbar_app_access, 'asset')) {
while (ob_get_level() > 0) {
ob_end_clean();
}
header('Location: ' . $server_url . app_home_path($app_registry, app_default_for_access($app_registry, $topbar_app_access)));
exit;
}
// Only persist to session when on a definitive app page
if ($topbar_current_app) {
$_SESSION['login_current_app'] = $topbar_current_app;
}
// Default session on first visit or neutral pages
if (empty($_SESSION['login_current_app'])) {
$_SESSION['login_current_app'] = ($topbar_app_access === 'accounting') ? 'accounting' : 'wms';
$_SESSION['login_current_app'] = app_default_for_access($app_registry, $topbar_app_access);
}
$topbar_show_wms = in_array($topbar_app_access, ['all', 'wms']);
$topbar_show_accounting = in_array($topbar_app_access, ['all', 'accounting']);
$topbar_show_wms = app_can_access($topbar_app_access, 'wms');
$topbar_show_accounting = app_can_access($topbar_app_access, 'accounting');
$topbar_switch_apps = array_filter(
$app_registry,
fn($key) => app_can_access($topbar_app_access, $key),
ARRAY_FILTER_USE_KEY
);
// Per-app colour. WMS keeps the compiled orange theme (#E66239); the others override it.
$_app_themes = [
'accounting' => ['main' => '#0284c7', 'rgb' => '2, 132, 199', 'dark' => '#0369a1', 'subtle' => '#e0f2fe', 'border' => '#bae6fd'],
'asset' => ['main' => '#16a34a', 'rgb' => '22, 163, 74', 'dark' => '#15803d', 'subtle' => '#dcfce7', 'border' => '#bbf7d0'],
];
$_theme_app = $_SESSION['login_current_app'] ?? 'wms';
$_theme = $_app_themes[$_theme_app] ?? null;
$_usage = (new UsageGuard($pdo1, (int)$_SESSION['login_company_id'], $packages))->getStatus();
$_usage_max_pct = max($_usage['daily_pct'], $_usage['weekly_pct']);
@@ -64,67 +88,67 @@ $_usage_warn = $_usage_max_pct >= 80 && $_usage_max_pct < 100;
$_usage_full = $_usage_max_pct >= 100;
?>
<?php if (($_SESSION['login_current_app'] ?? 'wms') === 'accounting'): ?>
<style id="theme-accounting">
<?php if ($_theme): ?>
<style id="theme-<?php echo $_theme_app; ?>">
:root {
--bs-primary: #0284c7;
--bs-primary-rgb: 2, 132, 199;
--bs-primary-bg-subtle: #e0f2fe;
--bs-primary-border-subtle: #bae6fd;
--bs-primary-text-emphasis: #0369a1;
--bs-focus-ring-color: rgba(2, 132, 199, .25);
--bs-primary: <?php echo $_theme['main']; ?>;
--bs-primary-rgb: <?php echo $_theme['rgb']; ?>;
--bs-primary-bg-subtle: <?php echo $_theme['subtle']; ?>;
--bs-primary-border-subtle: <?php echo $_theme['border']; ?>;
--bs-primary-text-emphasis: <?php echo $_theme['dark']; ?>;
--bs-focus-ring-color: rgba(<?php echo $_theme['rgb']; ?>, .25);
}
/* Sidebar active/hover — hardcoded in theme */
.sidebar .nav-link:hover,
.sidebar .nav-link.active {
color: #0284c7 !important;
background-color: rgba(2, 132, 199, 0.09) !important;
color: <?php echo $_theme['main']; ?> !important;
background-color: rgba(<?php echo $_theme['rgb']; ?>, 0.09) !important;
}
/* Buttons */
.btn-primary {
--bs-btn-bg: #0284c7;
--bs-btn-border-color: #0284c7;
--bs-btn-hover-bg: #0369a1;
--bs-btn-hover-border-color: #0369a1;
--bs-btn-active-bg: #0369a1;
--bs-btn-active-border-color: #0369a1;
--bs-btn-disabled-bg: #0284c7;
--bs-btn-disabled-border-color: #0284c7;
--bs-btn-focus-shadow-rgb: 2, 132, 199;
--bs-btn-bg: <?php echo $_theme['main']; ?>;
--bs-btn-border-color: <?php echo $_theme['main']; ?>;
--bs-btn-hover-bg: <?php echo $_theme['dark']; ?>;
--bs-btn-hover-border-color: <?php echo $_theme['dark']; ?>;
--bs-btn-active-bg: <?php echo $_theme['dark']; ?>;
--bs-btn-active-border-color: <?php echo $_theme['dark']; ?>;
--bs-btn-disabled-bg: <?php echo $_theme['main']; ?>;
--bs-btn-disabled-border-color: <?php echo $_theme['main']; ?>;
--bs-btn-focus-shadow-rgb: <?php echo $_theme['rgb']; ?>;
}
.btn-outline-primary {
--bs-btn-color: #0284c7;
--bs-btn-border-color: #0284c7;
--bs-btn-hover-bg: #0284c7;
--bs-btn-hover-border-color: #0284c7;
--bs-btn-active-bg: #0284c7;
--bs-btn-active-border-color: #0284c7;
--bs-btn-disabled-color: #0284c7;
--bs-btn-disabled-border-color: #0284c7;
--bs-btn-color: <?php echo $_theme['main']; ?>;
--bs-btn-border-color: <?php echo $_theme['main']; ?>;
--bs-btn-hover-bg: <?php echo $_theme['main']; ?>;
--bs-btn-hover-border-color: <?php echo $_theme['main']; ?>;
--bs-btn-active-bg: <?php echo $_theme['main']; ?>;
--bs-btn-active-border-color: <?php echo $_theme['main']; ?>;
--bs-btn-disabled-color: <?php echo $_theme['main']; ?>;
--bs-btn-disabled-border-color: <?php echo $_theme['main']; ?>;
}
.pagination {
--bs-pagination-active-bg: #0284c7;
--bs-pagination-active-border-color: #0284c7;
--bs-pagination-active-bg: <?php echo $_theme['main']; ?>;
--bs-pagination-active-border-color: <?php echo $_theme['main']; ?>;
}
.nav-pills { --bs-nav-pills-link-active-bg: #0284c7; }
.dropdown-menu { --bs-dropdown-link-active-bg: #0284c7; }
.nav-pills { --bs-nav-pills-link-active-bg: <?php echo $_theme['main']; ?>; }
.dropdown-menu { --bs-dropdown-link-active-bg: <?php echo $_theme['main']; ?>; }
.list-group {
--bs-list-group-active-bg: #0284c7;
--bs-list-group-active-border-color: #0284c7;
--bs-list-group-active-bg: <?php echo $_theme['main']; ?>;
--bs-list-group-active-border-color: <?php echo $_theme['main']; ?>;
}
.progress, .progress-stacked { --bs-progress-bar-bg: #0284c7; }
.progress, .progress-stacked { --bs-progress-bar-bg: <?php echo $_theme['main']; ?>; }
.table-primary {
--bs-table-color: #0284c7;
--bs-table-bg: rgba(2, 132, 199, 0.09);
--bs-table-border-color: rgba(2, 132, 199, 0.15);
--bs-table-striped-bg: rgba(2, 132, 199, 0.12);
--bs-table-active-bg: rgba(2, 132, 199, 0.15);
--bs-table-hover-bg: rgba(2, 132, 199, 0.12);
--bs-table-color: <?php echo $_theme['main']; ?>;
--bs-table-bg: rgba(<?php echo $_theme['rgb']; ?>, 0.09);
--bs-table-border-color: rgba(<?php echo $_theme['rgb']; ?>, 0.15);
--bs-table-striped-bg: rgba(<?php echo $_theme['rgb']; ?>, 0.12);
--bs-table-active-bg: rgba(<?php echo $_theme['rgb']; ?>, 0.15);
--bs-table-hover-bg: rgba(<?php echo $_theme['rgb']; ?>, 0.12);
}
thead.border-light th {
background-color: rgba(2, 132, 199, 0.09) !important;
color: #0284c7 !important;
border-color: rgba(2, 132, 199, 0.15) !important;
background-color: rgba(<?php echo $_theme['rgb']; ?>, 0.09) !important;
color: <?php echo $_theme['main']; ?> !important;
border-color: rgba(<?php echo $_theme['rgb']; ?>, 0.15) !important;
}
</style>
<?php endif; ?>
@@ -171,30 +195,24 @@ $_usage_full = $_usage_max_pct >= 100;
<ul class="list-unstyled d-flex align-items-center mb-0 gap-1">
<!-- App switcher -->
<?php if ($topbar_show_wms && $topbar_show_accounting): ?>
<?php if (count($topbar_switch_apps) > 1): ?>
<li class="dropdown">
<a class="position-relative btn-icon btn-sm btn-light btn rounded-circle" data-bs-toggle="dropdown"
aria-expanded="false" href="#" role="button" title="Switch App">
<i class="ti ti-layout-grid" style="font-size:18px;"></i>
</a>
<div class="dropdown-menu dropdown-menu-end p-2" style="min-width:170px;">
<div class="dropdown-menu dropdown-menu-end p-2" style="min-width:210px;">
<?php $_sess_app = $_SESSION['login_current_app'] ?? 'wms'; ?>
<a href="<?php echo $server_url?>dashboard/index.php"
class="dropdown-item rounded d-flex align-items-center gap-2 py-2 <?php echo $_sess_app === 'wms' ? 'active' : ''; ?>">
<i class="ti ti-box"></i>
<span>WMS</span>
<?php if ($_sess_app === 'wms'): ?>
<i class="ti ti-check ms-auto text-success"></i>
<?php endif; ?>
</a>
<a href="<?php echo $server_url?>ac_dashboard/index.php"
class="dropdown-item rounded d-flex align-items-center gap-2 py-2 <?php echo $_sess_app === 'accounting' ? 'active' : ''; ?>">
<i class="ti ti-calculator"></i>
<span>Accounting</span>
<?php if ($_sess_app === 'accounting'): ?>
<?php foreach ($topbar_switch_apps as $_app_key => $_app): ?>
<a href="<?php echo $server_url . app_home_path($app_registry, $_app_key); ?>"
class="dropdown-item rounded d-flex align-items-center gap-2 py-2 <?php echo $_sess_app === $_app_key ? 'active' : ''; ?>">
<i class="ti <?php echo htmlspecialchars($_app['icon'] ?? 'ti-apps', ENT_QUOTES, 'UTF-8'); ?>"></i>
<span><?php echo htmlspecialchars($_app['label'] ?? $_app_key, ENT_QUOTES, 'UTF-8'); ?></span>
<?php if ($_sess_app === $_app_key): ?>
<i class="ti ti-check ms-auto text-success"></i>
<?php endif; ?>
</a>
<?php endforeach; ?>
</div>
</li>
<?php endif; ?>