accounting modules

This commit is contained in:
Thanakorn S
2026-05-13 15:11:24 +07:00
parent 511360faa2
commit 04a683bd02
70 changed files with 4451 additions and 42 deletions
+39
View File
@@ -0,0 +1,39 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_sidebar_ac.php'; ?>
<!-- MAIN CONTENT -->
<main id="content" class="content py-15">
<div class="container-fluid">
<!-- Page header -->
<div class="row mb-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Accounting Dashboard</h1>
<p class="mb-0 text-muted">Financial overview</p>
</div>
</div>
<!-- Placeholder -->
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-calculator fs-1 mb-3 d-block"></i>
<p class="mb-0">Accounting dashboard coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/ChartOfAccounts.php';
$coa = new ChartOfAccounts($pdo2, $company_id);
$answer['output'] = $coa->getAll();
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/ChartOfAccounts.php';
$coa = new ChartOfAccounts($pdo2, $company_id);
$stats = $coa->getStats();
$answer = array_merge(['success' => 1], $stats);
exit(json_encode($answer));
+9
View File
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/DepartmentManager.php';
$dept = new DepartmentManager($pdo2, $company_id);
$answer['output'] = $dept->getAll();
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/DepartmentManager.php';
$dept = new DepartmentManager($pdo2, $company_id);
$stats = $dept->getStats();
$answer = array_merge(['success' => 1], $stats);
exit(json_encode($answer));
@@ -0,0 +1,22 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/ChartOfAccounts.php';
if (empty($data['account_code']) || empty($data['account_name']) || empty($data['account_type'])) {
exit(json_encode(['message' => 'Account code, name, and type are required']));
}
$coa = new ChartOfAccounts($pdo2, $company_id);
$id = (int)($data['id'] ?? 0);
if ($id) {
$coa->update($id, $data);
$answer['message'] = 'Account updated';
} else {
$coa->create($data);
$answer['message'] = 'Account created';
}
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,22 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/DepartmentManager.php';
if (empty($data['dept_code']) || empty($data['dept_name'])) {
exit(json_encode(['message' => 'Department code and name are required']));
}
$dept = new DepartmentManager($pdo2, $company_id);
$id = (int)($data['id'] ?? 0);
if ($id) {
$dept->update($id, $data);
$answer['message'] = 'Department updated';
} else {
$dept->create($data);
$answer['message'] = 'Department created';
}
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,40 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/PeriodManager.php';
$pm = new PeriodManager($pdo2, $company_id);
$action = $data['action_type'] ?? '';
if ($action === 'create_year') {
$year = (int)($data['year'] ?? date('Y'));
if ($year < 2000 || $year > 2100) exit(json_encode(['message' => 'Invalid year']));
$pm->createYear($year);
$answer['message'] = "All 12 periods for {$year} created";
} elseif ($action === 'create_single') {
$period = $data['period'] ?? '';
if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $period)) {
exit(json_encode(['message' => 'Invalid period format (YYYY-MM)']));
}
$pm->create($period);
$answer['message'] = "Period {$period} created";
} elseif ($action === 'open') {
$pm->open((int)($data['id'] ?? 0));
$answer['message'] = 'Period reopened';
} elseif ($action === 'close') {
$pm->close((int)($data['id'] ?? 0));
$answer['message'] = 'Period closed';
} elseif ($action === 'lock') {
$pm->lock((int)($data['id'] ?? 0));
$answer['message'] = 'Period locked';
} else {
exit(json_encode(['message' => 'Unknown action']));
}
$answer['success'] = 1;
exit(json_encode($answer));
+9
View File
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/PeriodManager.php';
$pm = new PeriodManager($pdo2, $company_id);
$answer['output'] = $pm->getAll();
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,9 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/PeriodManager.php';
$pm = new PeriodManager($pdo2, $company_id);
$stats = $pm->getStats();
$answer = array_merge(['success' => 1], $stats);
exit(json_encode($answer));
@@ -0,0 +1,16 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/ChartOfAccounts.php';
$id = (int)($data['id'] ?? 0);
if (!$id) {
exit(json_encode(['message' => 'Missing id']));
}
$coa = new ChartOfAccounts($pdo2, $company_id);
$coa->delete($id);
$answer['success'] = 1;
$answer['message'] = 'Account removed';
exit(json_encode($answer));
@@ -0,0 +1,14 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/DepartmentManager.php';
$id = (int)($data['id'] ?? 0);
if (!$id) exit(json_encode(['message' => 'Missing id']));
$dept = new DepartmentManager($pdo2, $company_id);
$dept->delete($id);
$answer['success'] = 1;
$answer['message'] = 'Department removed';
exit(json_encode($answer));
@@ -0,0 +1,19 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/ChartOfAccounts.php';
$id = (int)($data['id'] ?? 0);
if (!$id) {
exit(json_encode(['message' => 'Missing id']));
}
$coa = new ChartOfAccounts($pdo2, $company_id);
$row = $coa->getById($id);
if (!$row) {
exit(json_encode(['message' => 'Account not found']));
}
$answer['output'] = $row;
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,15 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes_ac/DepartmentManager.php';
$id = (int)($data['id'] ?? 0);
if (!$id) exit(json_encode(['message' => 'Missing id']));
$dept = new DepartmentManager($pdo2, $company_id);
$row = $dept->getById($id);
if (!$row) exit(json_encode(['message' => 'Department not found']));
$answer['output'] = $row;
$answer['success'] = 1;
exit(json_encode($answer));
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Balance Sheet</h1>
<p class="mb-0 text-muted">Assets, liabilities, and equity</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-table fs-1 mb-3 d-block"></i>
<p class="mb-0">Balance Sheet coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+272
View File
@@ -0,0 +1,272 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_master_sidebar.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">Chart of Accounts</h1>
<p class="mb-0">Manage your account code structure</p>
</div>
<div>
<a href="<?php echo $server_url?>accounting/manage_account.php" class="btn btn-primary">
<i class="ti ti-plus me-1"></i>Add Account
</a>
</div>
</div>
</div>
</div>
<!-- Stats -->
<div class="row g-5 mb-5">
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-primary bg-opacity-10 text-primary rounded-2">
<i class="ti ti-list-tree fs-4"></i>
</div>
<div>
<span>Total Accounts</span>
<h2 class="fw-bold mb-0" id="stat_total">—</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-success bg-opacity-10 text-success rounded-2">
<i class="ti ti-check fs-4"></i>
</div>
<div>
<span>Active</span>
<h2 class="fw-bold mb-0" id="stat_active">—</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-info bg-opacity-10 text-info rounded-2">
<i class="ti ti-trending-up fs-4"></i>
</div>
<div>
<span>Revenue Accounts</span>
<h2 class="fw-bold mb-0" id="stat_revenue">—</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-warning bg-opacity-10 text-warning rounded-2">
<i class="ti ti-trending-down fs-4"></i>
</div>
<div>
<span>Expense Accounts</span>
<h2 class="fw-bold mb-0" id="stat_expense">—</h2>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Table -->
<div class="row">
<div class="col-12">
<div class="card overflow-hidden">
<div class="card-body p-6">
<div class="mb-5 d-flex align-items-center justify-content-between gap-3 flex-wrap">
<div class="d-flex gap-2 flex-wrap">
<button class="btn btn-sm btn-outline-secondary active" onclick="filter_type('')" id="filter_all">All</button>
<button class="btn btn-sm btn-outline-secondary" onclick="filter_type('asset')" id="filter_asset">Asset</button>
<button class="btn btn-sm btn-outline-secondary" onclick="filter_type('liability')" id="filter_liability">Liability</button>
<button class="btn btn-sm btn-outline-secondary" onclick="filter_type('equity')" id="filter_equity">Equity</button>
<button class="btn btn-sm btn-outline-secondary" onclick="filter_type('revenue')" id="filter_revenue">Revenue</button>
<button class="btn btn-sm btn-outline-secondary" onclick="filter_type('expense')" id="filter_expense">Expense</button>
</div>
<div class="position-relative" style="min-width:240px;">
<input type="search" id="search_account" class="form-control ps-9" placeholder="Search code or name…">
<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="account_table">
<thead class="table-primary border-light">
<tr>
<th>Code</th>
<th>Name</th>
<th>Type</th>
<th>Level</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var search_term = '';
var active_type = '';
var type_badge = {
asset: '<span class="badge bg-primary bg-opacity-10 text-primary">Asset</span>',
liability: '<span class="badge bg-danger bg-opacity-10 text-danger">Liability</span>',
equity: '<span class="badge bg-info bg-opacity-10 text-info">Equity</span>',
revenue: '<span class="badge bg-success bg-opacity-10 text-success">Revenue</span>',
expense: '<span class="badge bg-warning bg-opacity-10 text-warning">Expense</span>',
};
var level_badge = {
item: '<span class="badge bg-primary">Item</span>',
parent: '<span class="badge bg-secondary">Parent</span>',
};
function retrieve_accounts() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/account.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
alasql('CREATE TABLE IF NOT EXISTS ac_account');
alasql.tables.ac_account.data = res.output || [];
change_page(1);
}
});
}
function change_page(page_num) {
var offset = (page_num - 1) * prop_limit;
var kw = search_term.toLowerCase();
var conditions = [];
if (kw) conditions.push(`(LOWER(account_code) LIKE '%${kw}%' OR LOWER(account_name) LIKE '%${kw}%')`);
if (active_type) conditions.push(`account_type = '${active_type}'`);
var where = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '';
var filtered = alasql(`SELECT * FROM ac_account ${where} ORDER BY account_code`);
var page = generate_pagination('ac_account', filtered.length);
$('table#account_table tfoot').html(page);
if (filtered.length === 0) {
$('table#account_table tbody').html(
'<tr><td colspan="7" class="text-center py-5 text-muted">No accounts found.</td></tr>'
);
return;
}
var page_data = filtered.slice(offset, offset + prop_limit);
var body = '';
$.each(page_data, function(i, item) {
var is_item = item.is_posting == 1;
var indent = is_item && item.parent_code ? 'ps-4' : '';
var prefix = is_item && item.parent_code ? '<i class="ti ti-corner-down-right text-muted me-1 small"></i>' : '';
body += `<tr class="${is_item ? '' : 'table-light'}">
<td class="py-3 fw-semibold ${indent}">${prefix}${escape_html(item.account_code)}</td>
<td class="py-3">${escape_html(item.account_name)}</td>
<td class="py-3">${type_badge[item.account_type] || item.account_type}</td>
<td class="py-3">${is_item ? level_badge.item : level_badge.parent}</td>
<td class="py-3">${item.status ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'}</td>
<td class="py-3">
<a href="<?php echo $server_url?>accounting/manage_account.php?id=${item.id}"><i class="ti ti-eye fs-5"></i></a>
<a href="javascript:;" class="link-danger ms-2" onclick="remove_account(${item.id}, $(this))"><i class="ti ti-trash fs-5"></i></a>
</td>
</tr>`;
});
$('table#account_table tbody').html(body);
}
function filter_type(type) {
active_type = type;
$('.btn[id^="filter_"]').removeClass('active');
$('#filter_' + (type || 'all')).addClass('active');
change_page(1);
}
function remove_account(id, element) {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/remove_account.php',
autoPrepare: true,
checkRequired: 0,
action: 'delete',
data: { id: id },
onSuccess: function() {
element.closest('tr').remove();
}
});
}
function retrieve_stats() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/account_stats.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
$('#stat_total').text(res.total || 0);
$('#stat_active').text(res.active || 0);
$('#stat_revenue').text(res.revenue || 0);
$('#stat_expense').text(res.expense || 0);
}
});
}
function debounce(fn, ms) {
var t;
return function() { var a = arguments, c = this; clearTimeout(t); t = setTimeout(function() { fn.apply(c, a); }, ms); };
}
$('#search_account').on('input', debounce(function() {
search_term = $(this).val().trim();
change_page(1);
}, 200));
$(async function() {
try {
await retrieve_stats();
await retrieve_accounts();
} catch(e) { console.log(e); }
});
</script>
</body>
</html>
+214
View File
@@ -0,0 +1,214 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_master_sidebar.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">Departments</h1>
<p class="mb-0">GL department dimensions for journal entries</p>
</div>
<div>
<a href="<?php echo $server_url?>accounting/manage_department.php" class="btn btn-primary">
<i class="ti ti-plus me-1"></i>Add Department
</a>
</div>
</div>
</div>
</div>
<!-- Stats -->
<div class="row g-5 mb-5">
<div class="col-xl-4 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-primary bg-opacity-10 text-primary rounded-2">
<i class="ti ti-building fs-4"></i>
</div>
<div>
<span>Total</span>
<h2 class="fw-bold mb-0" id="stat_total">—</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-success bg-opacity-10 text-success rounded-2">
<i class="ti ti-check fs-4"></i>
</div>
<div>
<span>Active</span>
<h2 class="fw-bold mb-0" id="stat_active">—</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-4 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-secondary bg-opacity-10 text-secondary rounded-2">
<i class="ti ti-x fs-4"></i>
</div>
<div>
<span>Inactive</span>
<h2 class="fw-bold mb-0" id="stat_inactive">—</h2>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Table -->
<div class="row">
<div class="col-12">
<div class="card overflow-hidden">
<div class="card-body p-6">
<div class="mb-5 d-flex justify-content-end">
<div class="position-relative" style="min-width:240px;">
<input type="search" id="search_dept" class="form-control ps-9" placeholder="Search code or name…">
<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="dept_table">
<thead class="table-primary border-light">
<tr>
<th>Code</th>
<th>Name</th>
<th>Description</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var search_term = '';
function retrieve_departments() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/department.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
alasql('CREATE TABLE IF NOT EXISTS ac_department');
alasql.tables.ac_department.data = res.output || [];
change_page(1);
}
});
}
function change_page(page_num) {
var offset = (page_num - 1) * prop_limit;
var kw = search_term.toLowerCase();
var filtered = kw
? alasql(`SELECT * FROM ac_department WHERE LOWER(dept_code) LIKE '%${kw}%' OR LOWER(dept_name) LIKE '%${kw}%' ORDER BY dept_code`)
: alasql(`SELECT * FROM ac_department ORDER BY dept_code`);
$('table#dept_table tfoot').html(generate_pagination('ac_department', filtered.length));
if (filtered.length === 0) {
$('table#dept_table tbody').html('<tr><td colspan="5" class="text-center py-5 text-muted">No departments found.</td></tr>');
return;
}
var page_data = filtered.slice(offset, offset + prop_limit);
var body = '';
$.each(page_data, function(i, item) {
body += `<tr>
<td class="py-3 fw-semibold">${escape_html(item.dept_code)}</td>
<td class="py-3">${escape_html(item.dept_name)}</td>
<td class="py-3 text-muted">${item.description ? escape_html(item.description) : '—'}</td>
<td class="py-3">${item.status ? '<span class="badge bg-success">Active</span>' : '<span class="badge bg-secondary">Inactive</span>'}</td>
<td class="py-3">
<a href="<?php echo $server_url?>accounting/manage_department.php?id=${item.id}"><i class="ti ti-eye fs-5"></i></a>
<a href="javascript:;" class="link-danger ms-2" onclick="remove_department(${item.id}, $(this))"><i class="ti ti-trash fs-5"></i></a>
</td>
</tr>`;
});
$('table#dept_table tbody').html(body);
}
function remove_department(id, element) {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/remove_department.php',
autoPrepare: true,
checkRequired: 0,
action: 'delete',
data: { id: id },
onSuccess: function() { element.closest('tr').remove(); }
});
}
function retrieve_stats() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/department_stats.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
$('#stat_total').text(res.total || 0);
$('#stat_active').text(res.active || 0);
$('#stat_inactive').text(res.inactive || 0);
}
});
}
function debounce(fn, ms) {
var t;
return function() { var a = arguments, c = this; clearTimeout(t); t = setTimeout(function() { fn.apply(c, a); }, ms); };
}
$('#search_dept').on('input', debounce(function() {
search_term = $(this).val().trim();
change_page(1);
}, 200));
$(async function() {
try {
await retrieve_stats();
await retrieve_departments();
} catch(e) { console.log(e); }
});
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">GL Movement</h1>
<p class="mb-0 text-muted">General ledger movement report</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-arrows-exchange fs-1 mb-3 d-block"></i>
<p class="mb-0">GL Movement coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+242
View File
@@ -0,0 +1,242 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_master_sidebar.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center mb-5 gap-3">
<a href="<?php echo $server_url?>accounting/chart_of_accounts.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">Manage Account</h1>
</div>
</div>
</div>
</div>
<div class="row" id="accountForm">
<div class="col-lg-7">
<div class="card">
<div class="card-body p-6">
<input type="hidden" id="id">
<div class="row g-4">
<div class="col-md-4">
<label class="form-label">Account Code <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="account_code" placeholder="e.g. 1100" required>
<div class="form-text">1xxx Asset · 2xxx Liability · 3xxx Equity · 4xxx Revenue · 5-9xxx Expense</div>
</div>
<div class="col-md-8">
<label class="form-label">Account Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="account_name" placeholder="e.g. Cash at Bank" required>
</div>
<div class="col-md-6">
<label class="form-label">Account Type <span class="text-danger">*</span></label>
<select class="form-select" id="account_type" required>
<option value="">— Auto-detected from code —</option>
<option value="asset">Asset</option>
<option value="liability">Liability</option>
<option value="equity">Equity</option>
<option value="revenue">Revenue</option>
<option value="expense">Expense</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Account Level <span class="text-danger">*</span></label>
<select class="form-select" id="is_posting" required>
<option value="1">Item — used in journal entries</option>
<option value="0">Parent — grouping / reports only</option>
</select>
</div>
<div class="col-md-12" id="parent_row">
<label class="form-label">Parent Account</label>
<select class="form-select" id="parent_code">
<option value="">— None (top-level parent) —</option>
</select>
<div class="form-text">Only parent-level accounts of the same type are shown.</div>
</div>
<div class="col-md-6">
<label class="form-label">Status</label>
<select class="form-select" id="status">
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</div>
</div>
<div class="d-flex gap-2 mt-5">
<button class="btn btn-primary" onclick="manage_account();" id="btn_submit">Create Account</button>
<button class="btn btn-secondary" onclick="reset_input('#accountForm');" id="btn_reset">Clear</button>
</div>
</div>
</div>
</div>
<!-- Guide card -->
<div class="col-lg-5">
<div class="card border-0 bg-light">
<div class="card-body p-5">
<h6 class="fw-semibold mb-3"><i class="ti ti-info-circle me-1"></i>Account Level Guide</h6>
<p class="small mb-3">
<strong>Parent</strong> accounts group items for reporting (e.g. <em>1000 Current Assets</em>).
They cannot be used in journal entries.
</p>
<p class="small mb-3">
<strong>Item</strong> accounts are the leaf-level codes posted in every journal entry
(e.g. <em>1110 Cash at Bank</em>).
</p>
<hr class="my-3">
<p class="small mb-1 fw-semibold">Standard ranges:</p>
<table class="table table-sm small mb-0">
<tbody>
<tr><td>1xxx</td><td>Asset</td></tr>
<tr><td>2xxx</td><td>Liability</td></tr>
<tr><td>3xxx</td><td>Equity</td></tr>
<tr><td>4xxx</td><td>Revenue</td></tr>
<tr><td>5–9xxx</td><td>Expense</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var all_accounts = [];
// ── Auto-detect type from code prefix ────────────────────────────────
function detect_type(code) {
var first = parseInt((code || '').charAt(0));
if (first === 1) return 'asset';
if (first === 2) return 'liability';
if (first === 3) return 'equity';
if (first === 4) return 'revenue';
if (first >= 5 && first <= 9) return 'expense';
return '';
}
$('#account_code').on('input', function() {
var type = detect_type($(this).val().trim());
if (type) {
$('#account_type').val(type);
refresh_parent_dropdown(type, null);
}
});
$('#account_type').on('change', function() {
refresh_parent_dropdown($(this).val(), null);
});
// Hide parent row when account level is set to Parent (is_posting=0)
$('#is_posting').on('change', function() {
if ($(this).val() == '0') {
$('#parent_row').hide();
$('#parent_code').val('');
} else {
$('#parent_row').show();
}
});
// ── Parent dropdown: filter to same type + is_posting=0 ─────────────
function refresh_parent_dropdown(type, selected_code) {
var opts = '<option value="">— None (top-level parent) —</option>';
var parents = all_accounts.filter(function(a) {
return a.is_posting == 0 && a.status == 1 && (!type || a.account_type === type);
});
$.each(parents, function(i, item) {
var sel = (selected_code && selected_code == item.account_code) ? 'selected' : '';
opts += `<option value="${escape_html(item.account_code)}" ${sel}>${escape_html(item.account_code)} — ${escape_html(item.account_name)}</option>`;
});
$('#parent_code').html(opts);
}
function load_all_accounts(selected_parent, selected_type) {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/account.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
all_accounts = res.output || [];
refresh_parent_dropdown(selected_type, selected_parent);
}
});
}
function manage_account() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/manage_account.php',
autoPrepare: true,
checkRequired: 1,
action: 'manage',
onSuccess: function() {
window.location.href = '<?php echo $server_url?>accounting/chart_of_accounts.php';
}
});
}
function retrieve_for_edit() {
<?php if (empty($_GET['id'])): ?>
return false;
<?php endif; ?>
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/retrieve_account.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
data: { id: <?php echo (int)$_GET['id']; ?> },
onSuccess: function(res) {
var item = res.output;
$('#id').val(item.id);
$('#account_code').val(item.account_code);
$('#account_name').val(item.account_name);
$('#account_type').val(item.account_type);
$('#is_posting').val(item.is_posting);
$('#status').val(item.status);
if (item.is_posting == 0) $('#parent_row').hide();
load_all_accounts(item.parent_code, item.account_type);
$('#btn_submit').text('Update Account');
$('#btn_reset').hide();
}
});
}
$(async function() {
try {
<?php if (empty($_GET['id'])): ?>
await load_all_accounts(null, null);
<?php else: ?>
await retrieve_for_edit();
<?php endif; ?>
} catch(e) { console.log(e); }
});
</script>
</body>
</html>
+112
View File
@@ -0,0 +1,112 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_master_sidebar.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex align-items-center mb-5 gap-3">
<a href="<?php echo $server_url?>accounting/departments.php" class="btn btn-light btn-sm">
<i class="ti ti-arrow-left me-1"></i>Back
</a>
<h1 class="fs-3 mb-0">Manage Department</h1>
</div>
</div>
</div>
<div class="row" id="deptForm">
<div class="col-lg-6">
<div class="card">
<div class="card-body p-6">
<input type="hidden" id="id">
<div class="row g-4">
<div class="col-md-4">
<label class="form-label">Department Code <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="dept_code" placeholder="e.g. HQ" required>
</div>
<div class="col-md-8">
<label class="form-label">Department Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="dept_name" placeholder="e.g. Head Office" required>
</div>
<div class="col-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="description" rows="3" placeholder="Optional description"></textarea>
</div>
<div class="col-md-6">
<label class="form-label">Status</label>
<select class="form-select" id="status">
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</div>
</div>
<div class="d-flex gap-2 mt-5">
<button class="btn btn-primary" onclick="manage_department();" id="btn_submit">Create Department</button>
<button class="btn btn-secondary" onclick="reset_input('#deptForm');" id="btn_reset">Clear</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
function manage_department() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/manage_department.php',
autoPrepare: true,
checkRequired: 1,
action: 'manage',
onSuccess: function() {
window.location.href = '<?php echo $server_url?>accounting/departments.php';
}
});
}
function retrieve_for_edit() {
<?php if (empty($_GET['id'])): ?>
return false;
<?php endif; ?>
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/retrieve_department.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
data: { id: <?php echo (int)$_GET['id']; ?> },
onSuccess: function(res) {
var item = res.output;
$('#id').val(item.id);
$('#dept_code').val(item.dept_code);
$('#dept_name').val(item.dept_name);
$('#description').val(item.description);
$('#status').val(item.status);
$('#btn_submit').text('Update Department');
$('#btn_reset').hide();
}
});
}
$(async function() {
try { await retrieve_for_edit(); } catch(e) { console.log(e); }
});
</script>
</body>
</html>
+274
View File
@@ -0,0 +1,274 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_master_sidebar.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">Accounting Periods</h1>
<p class="mb-0">Open, close, and lock monthly accounting periods</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-outline-primary" onclick="show_create_year_modal();">
<i class="ti ti-calendar-plus me-1"></i>Generate Year
</button>
<button class="btn btn-primary" onclick="show_create_modal();">
<i class="ti ti-plus me-1"></i>Add Period
</button>
</div>
</div>
</div>
</div>
<!-- Stats -->
<div class="row g-5 mb-5">
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-primary bg-opacity-10 text-primary rounded-2">
<i class="ti ti-calendar fs-4"></i>
</div>
<div><span>Total</span><h2 class="fw-bold mb-0" id="stat_total">—</h2></div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-success bg-opacity-10 text-success rounded-2">
<i class="ti ti-lock-open fs-4"></i>
</div>
<div><span>Open</span><h2 class="fw-bold mb-0" id="stat_open">—</h2></div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-warning bg-opacity-10 text-warning rounded-2">
<i class="ti ti-lock fs-4"></i>
</div>
<div><span>Closed</span><h2 class="fw-bold mb-0" id="stat_closed">—</h2></div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="card">
<div class="card-body p-6">
<div class="d-flex align-items-start gap-3">
<div class="icon-shape icon-lg bg-danger bg-opacity-10 text-danger rounded-2">
<i class="ti ti-lock-filled fs-4"></i>
</div>
<div><span>Locked</span><h2 class="fw-bold mb-0" id="stat_locked">—</h2></div>
</div>
</div>
</div>
</div>
</div>
<!-- Table -->
<div class="row">
<div class="col-12">
<div class="card overflow-hidden">
<div class="card-body p-6">
<div class="table-responsive">
<table class="table mb-0 text-nowrap table-hover table-centered" id="period_table">
<thead class="table-primary border-light">
<tr>
<th>Period</th>
<th>Status</th>
<th>Closed At</th>
<th>Locked At</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Add Single Period Modal -->
<div class="modal fade" id="createPeriodModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width:340px;">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Add Period</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label">Period (YYYY-MM)</label>
<input type="month" class="form-control" id="new_period">
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button class="btn btn-primary" onclick="create_single();">Create</button>
</div>
</div>
</div>
</div>
<!-- Generate Year Modal -->
<div class="modal fade" id="createYearModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width:340px;">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Generate Year Periods</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label">Year</label>
<input type="number" class="form-control" id="new_year" value="<?php echo date('Y'); ?>" min="2000" max="2100">
<div class="form-text">Creates all 12 months for the selected year (skips existing ones).</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button class="btn btn-primary" onclick="create_year();">Generate</button>
</div>
</div>
</div>
</div>
<?php require '../include_ending.php'; ?>
<script>
var status_badge = {
open: '<span class="badge bg-success">Open</span>',
closed: '<span class="badge bg-warning text-dark">Closed</span>',
locked: '<span class="badge bg-danger">Locked</span>',
};
function retrieve_periods() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/period.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var rows = res.output || [];
if (rows.length === 0) {
$('table#period_table tbody').html('<tr><td colspan="5" class="text-center py-5 text-muted">No periods yet. Use Generate Year to create them.</td></tr>');
return;
}
var body = '';
$.each(rows, function(i, item) {
var actions = '';
if (item.status === 'open') {
actions = `<button class="btn btn-sm btn-outline-warning" onclick="do_action('close', ${item.id})">Close</button>`;
} else if (item.status === 'closed') {
actions = `<button class="btn btn-sm btn-outline-success me-1" onclick="do_action('open', ${item.id})">Reopen</button>
<button class="btn btn-sm btn-outline-danger" onclick="do_action('lock', ${item.id})">Lock</button>`;
} else {
actions = '<span class="text-muted small">Permanent</span>';
}
body += `<tr>
<td class="py-3 fw-semibold">${escape_html(item.period)}</td>
<td class="py-3">${status_badge[item.status] || item.status}</td>
<td class="py-3 text-muted small">${item.closed_at || '—'}</td>
<td class="py-3 text-muted small">${item.locked_at || '—'}</td>
<td class="py-3">${actions}</td>
</tr>`;
});
$('table#period_table tbody').html(body);
}
});
}
function do_action(action_type, id) {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/manage_period.php',
autoPrepare: true,
checkRequired: 0,
action: 'manage',
data: { action_type: action_type, id: id },
onSuccess: function() {
retrieve_periods();
retrieve_stats();
}
});
}
function create_single() {
var period = $('#new_period').val();
if (!period) return;
ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/manage_period.php',
autoPrepare: true,
checkRequired: 0,
action: 'manage',
data: { action_type: 'create_single', period: period },
onSuccess: function() {
$('#createPeriodModal').modal('hide');
retrieve_periods();
retrieve_stats();
}
});
}
function create_year() {
var year = $('#new_year').val();
if (!year) return;
ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/manage_period.php',
autoPrepare: true,
checkRequired: 0,
action: 'manage',
data: { action_type: 'create_year', year: year },
onSuccess: function() {
$('#createYearModal').modal('hide');
retrieve_periods();
retrieve_stats();
}
});
}
function retrieve_stats() {
return ajax_request({
url: '<?php echo $server_url?>accounting/api/engine/period_stats.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
$('#stat_total').text(res.total || 0);
$('#stat_open').text(res.open_count || 0);
$('#stat_closed').text(res.closed_count || 0);
$('#stat_locked').text(res.locked_count || 0);
}
});
}
function show_create_modal() { $('#new_period').val(''); $('#createPeriodModal').modal('show'); }
function show_create_year_modal() { $('#createYearModal').modal('show'); }
$(async function() {
try {
await retrieve_stats();
await retrieve_periods();
} catch(e) { console.log(e); }
});
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">P&amp;L Statement</h1>
<p class="mb-0 text-muted">Profit and loss statement</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-chart-line fs-1 mb-3 d-block"></i>
<p class="mb-0">P&amp;L Statement coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Trial Balance</h1>
<p class="mb-0 text-muted">Period trial balance report</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-scale fs-1 mb-3 d-block"></i>
<p class="mb-0">Trial Balance coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">VAT Report</h1>
<p class="mb-0 text-muted">Value added tax report</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-receipt-tax fs-1 mb-3 d-block"></i>
<p class="mb-0">VAT Report coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+27 -5
View File
@@ -264,13 +264,27 @@ class OrderManager {
if (!$row) {
throw new Exception("Order not found.");
}
if ((int)$row['status'] !== 0) {
throw new Exception("Only draft orders can be edited.");
$cur_status = (int)$row['status'];
if ($cur_status !== 0 && $cur_status !== -2) {
throw new Exception("Only draft or pending orders can be edited.");
}
$log = json_decode($row['log'] ?? '[]', true) ?: [];
$log[] = $logging;
// Auto-promote Pending → Draft when all items have a warehouse assigned
$new_status = $cur_status;
if ($cur_status === -2) {
$all_have_warehouse = !empty($items) && array_reduce(
$items,
fn($ok, $item) => $ok && (int)($item['warehouse_id'] ?? 0) > 0,
true
);
if ($all_have_warehouse) {
$new_status = 0;
}
}
$this->pdo->prepare(
"UPDATE td_order SET
contact_id = :contact_id,
@@ -283,6 +297,7 @@ class OrderManager {
shipping_tracking_number = :shipping_tracking_number,
grand_total = :grand_total,
notes = :notes,
status = :status,
`log` = :log
WHERE id = :id AND company_id = :company_id"
)->execute([
@@ -296,6 +311,7 @@ class OrderManager {
':shipping_tracking_number' => $tracking_no,
':grand_total' => $grand_total,
':notes' => $data['notes'] ?? '',
':status' => $new_status,
':log' => json_encode($log),
':id' => $id,
':company_id' => $this->company_id,
@@ -306,22 +322,27 @@ class OrderManager {
} else {
$log = [$logging];
$quotation_id = (int)($data['quotation_id'] ?? 0);
// Quotation-converted orders start as Pending (-2); direct orders as Draft (0)
$init_status = $quotation_id > 0 ? -2 : 0;
$this->pdo->prepare(
"INSERT INTO td_order
(company_id, uuid, order_number, contact_id, order_date,
(company_id, uuid, quotation_id, order_number, contact_id, order_date,
status, payment_status, subtotal, discount, tax,
shipping_fee, shipping_tracking_number, grand_total, items, notes, `log`, created_at)
VALUES
(:company_id, :uuid, :order_number, :contact_id, :order_date,
0, 0, :subtotal, :discount, :tax,
(:company_id, :uuid, :quotation_id, :order_number, :contact_id, :order_date,
:status, 0, :subtotal, :discount, :tax,
:shipping_fee, :shipping_tracking_number, :grand_total, :items, :notes, :log, :created_at)"
)->execute([
':company_id' => $this->company_id,
':uuid' => bin2hex(random_bytes(16)),
':quotation_id' => $quotation_id,
':order_number' => $this->generateOrderNumber(),
':contact_id' => (int)($data['contact_id'] ?? 0),
':order_date' => $data['order_date'] ?? date('Y-m-d'),
':status' => $init_status,
':subtotal' => $subtotal,
':discount' => $discount,
':tax' => $tax,
@@ -539,6 +560,7 @@ class OrderManager {
"Cannot cancel an order that is processing or completed."
);
}
// Pending orders (-2) have no stock rows — skip the stock reversal loop below
// ── Guard: no active invoice ──────────────────────────────────────
$sth = $this->pdo->prepare(
@@ -0,0 +1,37 @@
<?php
/**
* PostingManager
*
* The ONLY bridge between WMS and the accounting engine.
* WMS API endpoints call methods here — nothing in WMS imports
* accounting classes directly.
*
* Usage in a WMS API endpoint (add at the end, after business doc saves):
* require_once '../../../assets/utils/classes/PostingManager.php';
* (new PostingManager($pdo2, $company_id))->postInvoice($invoice_id);
*
* If posting fails it logs silently — the business document is already saved.
*/
class PostingManager
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: postInvoice(int $invoice_id): void
// TODO: voidInvoice(int $invoice_id): void
// TODO: postPurchaseReceive(int $po_id): void
// TODO: voidPurchaseReceive(int $po_id): void
// TODO: postReceipt(int $receipt_id): void
// TODO: voidReceipt(int $receipt_id): void
// TODO: postPayment(int $payment_id): void
// TODO: voidPayment(int $payment_id): void
}
@@ -0,0 +1,23 @@
<?php
/**
* AccountingEngine
*
* Core double-entry validation and rules.
* Ensures every journal entry balances (total debit = total credit).
* All other accounting classes depend on this for validation.
*/
class AccountingEngine
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: validate journal lines balance (sum debit = sum credit)
// TODO: validate account codes exist and are posting accounts
// TODO: validate period is open
}
@@ -0,0 +1,141 @@
<?php
class ChartOfAccounts
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
public function getAll(): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_account
WHERE company_id = :cid
ORDER BY account_code ASC"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getByCode(string $account_code): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_account
WHERE company_id = :cid AND account_code = :code
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $account_code]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function getByType(string $account_type): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_account
WHERE company_id = :cid AND account_type = :type AND status = 1
ORDER BY account_code ASC"
);
$sth->execute([':cid' => $this->companyId, ':type' => $account_type]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getById(int $id): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_account
WHERE company_id = :cid AND id = :id
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function create(array $data): int
{
$sth = $this->pdo->prepare(
"INSERT INTO ac_account
(company_id, account_code, account_name, account_type, parent_code, is_posting, status)
VALUES
(:cid, :code, :name, :type, :parent, :posting, :status)"
);
$sth->execute([
':cid' => $this->companyId,
':code' => trim($data['account_code']),
':name' => trim($data['account_name']),
':type' => $data['account_type'],
':parent' => !empty($data['parent_code']) ? trim($data['parent_code']) : null,
':posting' => (int)($data['is_posting'] ?? 1),
':status' => (int)($data['status'] ?? 1),
]);
return (int)$this->pdo->lastInsertId();
}
public function update(int $id, array $data): void
{
$sth = $this->pdo->prepare(
"UPDATE ac_account SET
account_code = :code,
account_name = :name,
account_type = :type,
parent_code = :parent,
is_posting = :posting,
status = :status
WHERE company_id = :cid AND id = :id"
);
$sth->execute([
':cid' => $this->companyId,
':id' => $id,
':code' => trim($data['account_code']),
':name' => trim($data['account_name']),
':type' => $data['account_type'],
':parent' => !empty($data['parent_code']) ? trim($data['parent_code']) : null,
':posting' => (int)($data['is_posting'] ?? 1),
':status' => (int)($data['status'] ?? 1),
]);
}
public function delete(int $id): void
{
$sth = $this->pdo->prepare(
"UPDATE ac_account SET status = 0
WHERE company_id = :cid AND id = :id"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function isPostingAccount(string $account_code): bool
{
$sth = $this->pdo->prepare(
"SELECT is_posting FROM ac_account
WHERE company_id = :cid AND account_code = :code AND status = 1
LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':code' => $account_code]);
return (bool)$sth->fetchColumn();
}
public function getStats(): array
{
$sth = $this->pdo->prepare(
"SELECT
COUNT(*) AS total,
SUM(status = 1) AS active,
SUM(status = 0) AS inactive,
SUM(account_type = 'asset') AS asset,
SUM(account_type = 'liability') AS liability,
SUM(account_type = 'equity') AS equity,
SUM(account_type = 'revenue') AS revenue,
SUM(account_type = 'expense') AS expense
FROM ac_account
WHERE company_id = :cid"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetch(PDO::FETCH_ASSOC) ?: [];
}
}
@@ -0,0 +1,91 @@
<?php
class DepartmentManager
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
public function getAll(): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_department
WHERE company_id = :cid
ORDER BY dept_code ASC"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getById(int $id): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_department
WHERE company_id = :cid AND id = :id LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function create(array $data): int
{
$sth = $this->pdo->prepare(
"INSERT INTO ac_department (company_id, dept_code, dept_name, description, status)
VALUES (:cid, :code, :name, :desc, :status)"
);
$sth->execute([
':cid' => $this->companyId,
':code' => trim($data['dept_code']),
':name' => trim($data['dept_name']),
':desc' => $data['description'] ?? null,
':status' => (int)($data['status'] ?? 1),
]);
return (int)$this->pdo->lastInsertId();
}
public function update(int $id, array $data): void
{
$sth = $this->pdo->prepare(
"UPDATE ac_department SET
dept_code = :code,
dept_name = :name,
description = :desc,
status = :status
WHERE company_id = :cid AND id = :id"
);
$sth->execute([
':cid' => $this->companyId,
':id' => $id,
':code' => trim($data['dept_code']),
':name' => trim($data['dept_name']),
':desc' => $data['description'] ?? null,
':status' => (int)($data['status'] ?? 1),
]);
}
public function delete(int $id): void
{
$sth = $this->pdo->prepare(
"UPDATE ac_department SET status = 0
WHERE company_id = :cid AND id = :id"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function getStats(): array
{
$sth = $this->pdo->prepare(
"SELECT COUNT(*) AS total,
SUM(status = 1) AS active,
SUM(status = 0) AS inactive
FROM ac_department WHERE company_id = :cid"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetch(PDO::FETCH_ASSOC) ?: [];
}
}
@@ -0,0 +1,23 @@
<?php
/**
* FinancialReports
*
* Generates all financial statements from ac_balance + ac_journal_line.
* Read-only — no writes.
*/
class FinancialReports
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: getTrialBalance(string $from_period, string $to_period, ?int $dept_id): array
// TODO: getProfitAndLoss(string $from_period, string $to_period, ?int $dept_id): array
// TODO: getBalanceSheet(string $as_of_period, ?int $dept_id): array
// TODO: getGLMovement(string $account_code, string $from_period, string $to_period): array
}
@@ -0,0 +1,25 @@
<?php
/**
* JournalManager
*
* Create, read, and void journal entries in ac_journal + ac_journal_line.
* Updates ac_balance after each post.
* Never called directly by WMS — goes through PostingManager.
*/
class JournalManager
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: create(array $header, array $lines): int — returns journal_id
// TODO: void(int $journal_id): void
// TODO: getBySource(string $source_type, int $source_id): ?array
// TODO: getById(int $journal_id): ?array
// TODO: updateBalance(string $period, string $account_code, ?int $dept_id, float $debit, float $credit): void
}
@@ -0,0 +1,120 @@
<?php
class PeriodManager
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
public function getAll(): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_period
WHERE company_id = :cid
ORDER BY period DESC"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetchAll(PDO::FETCH_ASSOC);
}
public function getById(int $id): ?array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_period WHERE company_id = :cid AND id = :id LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':id' => $id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
return $row ?: null;
}
public function isOpen(string $period): bool
{
$sth = $this->pdo->prepare(
"SELECT status FROM ac_period
WHERE company_id = :cid AND period = :period LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':period' => $period]);
$status = $sth->fetchColumn();
return $status === 'open';
}
// Get existing period or auto-create it as open
public function getOrCreate(string $period): array
{
$sth = $this->pdo->prepare(
"SELECT * FROM ac_period WHERE company_id = :cid AND period = :period LIMIT 1"
);
$sth->execute([':cid' => $this->companyId, ':period' => $period]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if ($row) return $row;
$this->pdo->prepare(
"INSERT INTO ac_period (company_id, period, status) VALUES (:cid, :period, 'open')"
)->execute([':cid' => $this->companyId, ':period' => $period]);
return $this->getOrCreate($period);
}
// Create a single period (manual)
public function create(string $period): void
{
$this->pdo->prepare(
"INSERT IGNORE INTO ac_period (company_id, period, status) VALUES (:cid, :period, 'open')"
)->execute([':cid' => $this->companyId, ':period' => $period]);
}
// Bulk-create all 12 months of a year
public function createYear(int $year): void
{
$sth = $this->pdo->prepare(
"INSERT IGNORE INTO ac_period (company_id, period, status) VALUES (:cid, :period, 'open')"
);
for ($m = 1; $m <= 12; $m++) {
$sth->execute([
':cid' => $this->companyId,
':period' => sprintf('%04d-%02d', $year, $m),
]);
}
}
public function open(int $id): void
{
$this->pdo->prepare(
"UPDATE ac_period SET status = 'open', closed_at = NULL
WHERE company_id = :cid AND id = :id AND status = 'closed'"
)->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function close(int $id): void
{
$this->pdo->prepare(
"UPDATE ac_period SET status = 'closed', closed_at = NOW()
WHERE company_id = :cid AND id = :id AND status = 'open'"
)->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function lock(int $id): void
{
$this->pdo->prepare(
"UPDATE ac_period SET status = 'locked', locked_at = NOW()
WHERE company_id = :cid AND id = :id AND status = 'closed'"
)->execute([':cid' => $this->companyId, ':id' => $id]);
}
public function getStats(): array
{
$sth = $this->pdo->prepare(
"SELECT COUNT(*) AS total,
SUM(status = 'open') AS open_count,
SUM(status = 'closed') AS closed_count,
SUM(status = 'locked') AS locked_count
FROM ac_period WHERE company_id = :cid"
);
$sth->execute([':cid' => $this->companyId]);
return $sth->fetch(PDO::FETCH_ASSOC) ?: [];
}
}
@@ -0,0 +1,23 @@
<?php
/**
* TaxReportManager
*
* Generates VAT and withholding tax reports from journal lines
* tagged to tax accounts (output VAT, input VAT, WHT).
* Read-only — no writes.
*/
class TaxReportManager
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: getOutputVAT(string $from_period, string $to_period): array
// TODO: getInputVAT(string $from_period, string $to_period): array
// TODO: getWithholdingTax(string $from_period, string $to_period): array
}
@@ -0,0 +1,21 @@
<?php
/**
* InvoicePosting
*
* Builds the journal entry for a posted sales invoice.
* DR: Accounts Receivable
* CR: Revenue + Output VAT
*/
class InvoicePosting
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: build(int $invoice_id): array — returns [header, lines] ready for JournalManager
}
@@ -0,0 +1,21 @@
<?php
/**
* PaymentPosting
*
* Builds the journal entry when a supplier payment is recorded.
* DR: Accounts Payable
* CR: Bank / Cash
*/
class PaymentPosting
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: build(int $payment_id): array — returns [header, lines] ready for JournalManager
}
@@ -0,0 +1,21 @@
<?php
/**
* PurchasePosting
*
* Builds the journal entry when a Purchase Order is received (GRN).
* DR: Inventory
* CR: Accounts Payable
*/
class PurchasePosting
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: build(int $po_id): array — returns [header, lines] ready for JournalManager
}
@@ -0,0 +1,21 @@
<?php
/**
* ReceiptPosting
*
* Builds the journal entry when a customer receipt is recorded.
* DR: Bank / Cash
* CR: Accounts Receivable
*/
class ReceiptPosting
{
private PDO $pdo;
private int $companyId;
public function __construct(PDO $pdo, int $company_id)
{
$this->pdo = $pdo;
$this->companyId = $company_id;
}
// TODO: build(int $receipt_id): array — returns [header, lines] ready for JournalManager
}
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Payments</h1>
<p class="mb-0 text-muted">Payments to suppliers</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-credit-card fs-1 mb-3 d-block"></i>
<p class="mb-0">Payments coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Purchase Invoices</h1>
<p class="mb-0 text-muted">Supplier invoices</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-file-invoice fs-1 mb-3 d-block"></i>
<p class="mb-0">Purchase Invoices coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Purchase Orders</h1>
<p class="mb-0 text-muted">Purchase order management</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-clipboard-list fs-1 mb-3 d-block"></i>
<p class="mb-0">Purchase Orders coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Purchase Requests</h1>
<p class="mb-0 text-muted">Internal purchase requests</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-notes fs-1 mb-3 d-block"></i>
<p class="mb-0">Purchase Requests coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Payments</h1>
<p class="mb-0 text-muted">Cash and bank payments</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-credit-card fs-1 mb-3 d-block"></i>
<p class="mb-0">Payments coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Receipts</h1>
<p class="mb-0 text-muted">Cash and bank receipts</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-cash fs-1 mb-3 d-block"></i>
<p class="mb-0">Receipts coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+50 -6
View File
@@ -1,7 +1,20 @@
<?php
$current_page = basename($_SERVER['PHP_SELF']);
$cp = $_SERVER['PHP_SELF'];
function master_active(...$frags) {
global $cp;
foreach ($frags as $f) { if (str_contains($cp, $f)) return 'active'; }
return '';
}
$master_role = $_SESSION['login_role'] ?? 'viewer';
$master_can_admin = in_array($master_role, ['owner', 'admin'], true);
$master_app_access = $_SESSION['login_app_access'] ?? 'wms';
$master_show_accounting = in_array($master_app_access, ['all', 'accounting']);
$master_back_url = ($_SESSION['login_current_app'] ?? 'wms') === 'accounting'
? $server_url . 'ac_dashboard/index.php'
: $server_url . 'dashboard/index.php';
$master_back_label = ($_SESSION['login_current_app'] ?? 'wms') === 'accounting'
? 'Back to Accounting'
: 'Back to WMS';
?>
<!-- SIDEBAR -->
@@ -23,7 +36,7 @@
</li>
<li>
<a class="nav-link <?php echo in_array($current_page, ['product.php', 'manage_product.php', 'manage_category.php']) ? 'active' : ''; ?>"
<a class="nav-link <?php echo master_active('/inventory/product', '/inventory/manage_product', '/inventory/manage_category'); ?>"
href="<?php echo $server_url?>inventory/product.php">
<i class="ti ti-category"></i>
<span class="nav-text">Products</span>
@@ -32,7 +45,7 @@
<?php if ($master_can_admin): ?>
<li>
<a class="nav-link <?php echo in_array($current_page, ['warehouse.php', 'manage_warehouse.php', 'manage_storage.php']) ? 'active' : ''; ?>"
<a class="nav-link <?php echo master_active('/inventory/warehouse', '/inventory/manage_warehouse', '/inventory/manage_storage'); ?>"
href="<?php echo $server_url?>inventory/warehouse.php">
<i class="ti ti-map-pin"></i>
<span class="nav-text">Warehouse Location</span>
@@ -41,22 +54,53 @@
<?php endif; ?>
<li>
<a class="nav-link <?php echo in_array($current_page, ['contact.php', 'manage_contact.php', 'manage_contact_type.php']) ? 'active' : ''; ?>"
<a class="nav-link <?php echo master_active('/contact/'); ?>"
href="<?php echo $server_url?>contact/contact.php">
<i class="ti ti-users"></i>
<span class="nav-text">Contact</span>
</a>
</li>
<!-- Accounting -->
<?php if ($master_show_accounting): ?>
<li class="nav-text-space mt-2">
<small class="nav-text text-muted">Accounting</small>
</li>
<li>
<a class="nav-link <?php echo master_active('/accounting/chart_of_accounts', '/accounting/manage_account'); ?>"
href="<?php echo $server_url?>accounting/chart_of_accounts.php">
<i class="ti ti-list-tree"></i>
<span class="nav-text">Chart of Accounts</span>
</a>
</li>
<li>
<a class="nav-link <?php echo master_active('/accounting/departments', '/accounting/manage_department'); ?>"
href="<?php echo $server_url?>accounting/departments.php">
<i class="ti ti-building"></i>
<span class="nav-text">Departments</span>
</a>
</li>
<li>
<a class="nav-link <?php echo master_active('/accounting/periods'); ?>"
href="<?php echo $server_url?>accounting/periods.php">
<i class="ti ti-calendar"></i>
<span class="nav-text">Accounting Periods</span>
</a>
</li>
<?php endif; ?>
<!-- Navigation -->
<li class="nav-text-space mt-2">
<small class="nav-text text-muted">Navigation</small>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>dashboard/index.php">
<a class="nav-link" href="<?php echo $master_back_url; ?>">
<i class="ti ti-arrow-left"></i>
<span class="nav-text">Back to App</span>
<span class="nav-text"><?php echo $master_back_label; ?></span>
</a>
</li>
+22 -16
View File
@@ -1,6 +1,12 @@
<?php
$sidebar_role = $_SESSION['login_role'] ?? 'viewer';
$sidebar_can_staff = in_array($sidebar_role, ['owner', 'admin', 'staff'], true);
$cp = $_SERVER['PHP_SELF'];
function wms_active(...$frags) {
global $cp;
foreach ($frags as $f) { if (str_contains($cp, $f)) return 'active'; }
return '';
}
?>
<!-- SIDEBAR -->
<aside id="sidebar" class="sidebar overflow-y-auto">
@@ -21,14 +27,14 @@
</li>
<li>
<a class="nav-link active" href="<?php echo $server_url?>dashboard/index.php">
<a class="nav-link <?php echo wms_active('/dashboard/index'); ?>" href="<?php echo $server_url?>dashboard/index.php">
<i class="ti ti-home"></i>
<span class="nav-text">Dashboard</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>dashboard/low_stock_products.php">
<a class="nav-link <?php echo wms_active('low_stock'); ?>" href="<?php echo $server_url?>dashboard/low_stock_products.php">
<i class="ti ti-alert-triangle"></i>
<span class="nav-text">Low Stock</span>
</a>
@@ -40,7 +46,7 @@
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/stock_overview.php">
<a class="nav-link <?php echo wms_active('stock_overview'); ?>" href="<?php echo $server_url?>ics/stock_overview.php">
<i class="ti ti-clipboard"></i>
<span class="nav-text">Stock Overview</span>
</a>
@@ -48,35 +54,35 @@
<?php if ($sidebar_can_staff): ?>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/stock_in.php">
<a class="nav-link <?php echo wms_active('stock_in'); ?>" href="<?php echo $server_url?>ics/stock_in.php">
<i class="ti ti-package-import"></i>
<span class="nav-text">Stock In</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/stock_out.php">
<a class="nav-link <?php echo wms_active('stock_out'); ?>" href="<?php echo $server_url?>ics/stock_out.php">
<i class="ti ti-package-export"></i>
<span class="nav-text">Stock Out</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/stock_transfer.php">
<a class="nav-link <?php echo wms_active('stock_transfer'); ?>" href="<?php echo $server_url?>ics/stock_transfer.php">
<i class="ti ti-package"></i>
<span class="nav-text">Stock Transfer</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/sku_barcode_label.php">
<a class="nav-link <?php echo wms_active('sku_barcode'); ?>" href="<?php echo $server_url?>ics/sku_barcode_label.php">
<i class="ti ti-barcode"></i>
<span class="nav-text">SKU Labels</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>ics/location_barcode_label.php">
<a class="nav-link <?php echo wms_active('location_barcode'); ?>" href="<?php echo $server_url?>ics/location_barcode_label.php">
<i class="ti ti-map-pin-code"></i>
<span class="nav-text">Location Labels</span>
</a>
@@ -89,28 +95,28 @@
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>order/order.php">
<a class="nav-link <?php echo wms_active('/order/order', '/order/manage_order', '/order/confirm_order'); ?>" href="<?php echo $server_url?>order/order.php">
<i class="ti ti-shopping-cart"></i>
<span class="nav-text">Sales Orders</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>order/return.php">
<a class="nav-link <?php echo wms_active('/order/return', 'manage_return'); ?>" href="<?php echo $server_url?>order/return.php">
<i class="ti ti-arrow-back-up"></i>
<span class="nav-text">Returns</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>order/invoice.php">
<a class="nav-link <?php echo wms_active('/order/invoice', 'manage_invoice', 'print_invoice'); ?>" href="<?php echo $server_url?>order/invoice.php">
<i class="ti ti-file-invoice"></i>
<span class="nav-text">Sales Invoices</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>po/po.php">
<a class="nav-link <?php echo wms_active('/po/'); ?>" href="<?php echo $server_url?>po/po.php">
<i class="ti ti-clipboard-list"></i>
<span class="nav-text">Purchase Orders</span>
</a>
@@ -122,28 +128,28 @@
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>reports/stock_movement.php">
<a class="nav-link <?php echo wms_active('stock_movement'); ?>" href="<?php echo $server_url?>reports/stock_movement.php">
<i class="ti ti-chart-bar"></i>
<span class="nav-text">Stock Movement</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>reports/expired_stock.php">
<a class="nav-link <?php echo wms_active('expired_stock'); ?>" href="<?php echo $server_url?>reports/expired_stock.php">
<i class="ti ti-clock"></i>
<span class="nav-text">Expired Stock</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>reports/occupy_rack.php">
<a class="nav-link <?php echo wms_active('occupy_rack'); ?>" href="<?php echo $server_url?>reports/occupy_rack.php">
<i class="ti ti-layout-grid"></i>
<span class="nav-text">Rack Occupancy</span>
</a>
</li>
<li>
<a class="nav-link" href="<?php echo $server_url?>reports/product_lot.php">
<a class="nav-link <?php echo wms_active('product_lot'); ?>" href="<?php echo $server_url?>reports/product_lot.php">
<i class="ti ti-packages"></i>
<span class="nav-text">Product Lots</span>
</a>
+183
View File
@@ -0,0 +1,183 @@
<?php
$sidebar_role = $_SESSION['login_role'] ?? 'viewer';
$sidebar_can_staff = in_array($sidebar_role, ['owner', 'admin', 'staff'], true);
$cp = $_SERVER['PHP_SELF'];
function ac_active(...$frags) {
global $cp;
foreach ($frags as $f) { if (str_contains($cp, $f)) return 'active'; }
return '';
}
?>
<!-- SIDEBAR -->
<aside id="sidebar" class="sidebar overflow-y-auto">
<div class="logo-area">
<a href="index.html" class="d-inline-flex">
<img src="<?php echo $server_url?>assets/images/favicon.png" alt="" width="40"/>
<span class="logo-text ms-2">
<img src="<?php echo $server_url?>assets/images/logo.png" alt="" height="40" style="padding: 5px 0;"/>
</span>
</a>
</div>
<ul class="nav flex-column">
<!-- Main -->
<li class="nav-text-space">
<small class="nav-text text-muted">Main</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('/ac_dashboard/'); ?>" href="<?php echo $server_url?>ac_dashboard/index.php">
<i class="ti ti-home"></i>
<span class="nav-text">Dashboard</span>
</a>
</li>
<!-- Revenue -->
<li class="nav-text-space">
<small class="nav-text text-muted">Revenue</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('/revenue/quotation'); ?>" href="<?php echo $server_url?>revenue/quotation.php">
<i class="ti ti-file-description"></i>
<span class="nav-text">Quotations</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/revenue/order'); ?>" href="<?php echo $server_url?>revenue/order.php">
<i class="ti ti-shopping-cart"></i>
<span class="nav-text">Sales Orders</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/revenue/invoice'); ?>" href="<?php echo $server_url?>revenue/invoice.php">
<i class="ti ti-file-invoice"></i>
<span class="nav-text">Sales Invoices</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/revenue/receipt'); ?>" href="<?php echo $server_url?>revenue/receipt.php">
<i class="ti ti-cash"></i>
<span class="nav-text">Receipts</span>
</a>
</li>
<!-- Purchase -->
<li class="nav-text-space">
<small class="nav-text text-muted">Purchase</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('/expense/purchase_request'); ?>" href="<?php echo $server_url?>expense/purchase_request.php">
<i class="ti ti-notes"></i>
<span class="nav-text">Purchase Requests</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/expense/purchase_order'); ?>" href="<?php echo $server_url?>expense/purchase_order.php">
<i class="ti ti-clipboard-list"></i>
<span class="nav-text">Purchase Orders</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/expense/purchase_invoice'); ?>" href="<?php echo $server_url?>expense/purchase_invoice.php">
<i class="ti ti-file-invoice"></i>
<span class="nav-text">Purchase Invoices</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/expense/payment'); ?>" href="<?php echo $server_url?>expense/payment.php">
<i class="ti ti-credit-card"></i>
<span class="nav-text">Payments</span>
</a>
</li>
<!-- Finance -->
<li class="nav-text-space">
<small class="nav-text text-muted">Finance</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('/finance/receipt'); ?>" href="<?php echo $server_url?>finance/receipt.php">
<i class="ti ti-cash"></i>
<span class="nav-text">Receipts</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/finance/payment'); ?>" href="<?php echo $server_url?>finance/payment.php">
<i class="ti ti-credit-card"></i>
<span class="nav-text">Payments</span>
</a>
</li>
<!-- Journal Entries -->
<li class="nav-text-space">
<small class="nav-text text-muted">Journal Entries</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('/journal/index'); ?>" href="<?php echo $server_url?>journal/index.php">
<i class="ti ti-books"></i>
<span class="nav-text">Journal List</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('/journal/new'); ?>" href="<?php echo $server_url?>journal/new.php">
<i class="ti ti-plus"></i>
<span class="nav-text">New Entry</span>
</a>
</li>
<!-- Reports -->
<li class="nav-text-space">
<small class="nav-text text-muted">Reports</small>
</li>
<li>
<a class="nav-link <?php echo ac_active('trial_balance'); ?>" href="<?php echo $server_url?>accounting/trial_balance.php">
<i class="ti ti-scale"></i>
<span class="nav-text">Trial Balance</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('pl_statement'); ?>" href="<?php echo $server_url?>accounting/pl_statement.php">
<i class="ti ti-chart-line"></i>
<span class="nav-text">P&amp;L Statement</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('balance_sheet'); ?>" href="<?php echo $server_url?>accounting/balance_sheet.php">
<i class="ti ti-table"></i>
<span class="nav-text">Balance Sheet</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('gl_movement'); ?>" href="<?php echo $server_url?>accounting/gl_movement.php">
<i class="ti ti-arrows-exchange"></i>
<span class="nav-text">GL Movement</span>
</a>
</li>
<li>
<a class="nav-link <?php echo ac_active('vat_report'); ?>" href="<?php echo $server_url?>accounting/vat_report.php">
<i class="ti ti-receipt-tax"></i>
<span class="nav-text">VAT Report</span>
</a>
</li>
<li><br><br><br><br></li>
</ul>
</aside>
+59
View File
@@ -17,6 +17,36 @@ $topbar_picture = $_SESSION['login_profile_picture'] ?? '';
$topbar_avatar_src = $topbar_picture
? htmlspecialchars($server_url . 'uploads/profile/' . $topbar_picture, ENT_QUOTES, 'UTF-8')
: htmlspecialchars($server_url . 'assets/images/avatar-1.jpg', ENT_QUOTES, 'UTF-8');
// ── App switcher ──────────────────────────────────────────────────────────────
$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/'];
$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; }
}
}
// 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';
}
$topbar_show_wms = in_array($topbar_app_access, ['all', 'wms']);
$topbar_show_accounting = in_array($topbar_app_access, ['all', 'accounting']);
?>
<div id="overlay" class="overlay"></div>
@@ -59,6 +89,35 @@ $topbar_avatar_src = $topbar_picture
<!-- Navbar nav -->
<ul class="list-unstyled d-flex align-items-center mb-0 gap-1">
<!-- App switcher -->
<?php if ($topbar_show_wms && $topbar_show_accounting): ?>
<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;">
<?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'): ?>
<i class="ti ti-check ms-auto text-success"></i>
<?php endif; ?>
</a>
</div>
</li>
<?php endif; ?>
<!-- Bell icon -->
<li>
<a class="position-relative btn-icon btn-sm btn-light btn rounded-circle" data-bs-toggle="dropdown"
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Journal List</h1>
<p class="mb-0 text-muted">All journal entries</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-books fs-1 mb-3 d-block"></i>
<p class="mb-0">Journal List coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">New Journal Entry</h1>
<p class="mb-0 text-muted">Create a manual journal entry</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-plus fs-1 mb-3 d-block"></i>
<p class="mb-0">New Journal Entry coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+1
View File
@@ -124,6 +124,7 @@ $_SESSION["login_name"] = $temp["name"];
$_SESSION["login_surname"] = $temp["surname"];
$_SESSION["login_company_id"] = $temp["default_company"];
$_SESSION["login_profile_picture"] = $temp["profile_picture"] ?? '';
$_SESSION["login_app_access"] = $temp["app_access"] ?? 'wms';
$role_sth = $pdo1->prepare(
"SELECT role FROM company_map_user
+5 -2
View File
@@ -2,9 +2,12 @@
require '../session.php';
require '../config.php';
require '../include_header.php';
// successful login
// successful login — redirect based on app_access
if(!empty($_SESSION["login_status"])){
header('Location: '.$server_url.'dashboard/index.php');
$redirect = ($_SESSION['login_app_access'] ?? 'wms') === 'accounting'
? $server_url . 'ac_dashboard/index.php'
: $server_url . 'dashboard/index.php';
header('Location: ' . $redirect);
exit;
}
+10 -1
View File
@@ -9,10 +9,19 @@
try {
$new_id = null;
$quotation_id = (int)($data['quotation_id'] ?? 0);
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, &$new_id) {
dbTransaction($pdo2, function($pdo) use ($data, $company_id, $logging, $quotation_id, &$new_id) {
$order = new OrderManager($pdo, $company_id);
$new_id = $order->saveOrder($data, $logging);
// On new order created from a quotation: link both records
if ($new_id && $quotation_id > 0) {
$pdo->prepare(
"UPDATE ac_quotation SET order_id = :order_id, status = 5
WHERE id = :id AND company_id = :cid AND status = 2"
)->execute([':order_id' => $new_id, ':id' => $quotation_id, ':cid' => $company_id]);
}
});
$answer['success'] = 1;
+14 -6
View File
@@ -244,6 +244,7 @@
// ── Status helpers ────────────────────────────────────────────────────────
function order_status_badge(status) {
const map = {
'-2': '<span class="badge bg-danger">Pending — Awaiting Warehouse</span>',
'-1': '<span class="badge bg-secondary">Cancelled</span>',
'0': '<span class="badge bg-warning text-dark">Draft</span>',
'1': '<span class="badge bg-info text-white">Confirmed</span>',
@@ -380,19 +381,26 @@
has_active_invoice = has_active_invoice || false;
returnable_count = returnable_count ?? 1;
fulfillment_status = parseInt(fulfillment_status) || 0;
var is_pending = status === -2;
var is_draft = status === 0;
var can_confirm = status === 0;
var can_cancel = status === 0 || status === 1;
var is_editable = is_pending || is_draft; // both Pending and Draft are editable
var can_confirm = status === 0; // only Draft can be confirmed (not Pending)
var can_cancel = status === -2 || status === 0 || status === 1;
var is_cancelled = status === -1;
var is_completed = status === 3;
var can_fulfill = status >= 1 && !is_cancelled && !is_completed;
// Edit controls
$('#btn_add_item, #contact, #order_date, #notes, #discount, #tax, #shipping_fee')
.prop('disabled', !is_draft);
$('#shipping_tracking_number').prop('disabled', !(is_draft || can_fulfill));
$('.item_sku, .item_qty, .item_price, .item_warehouse').prop('disabled', !is_draft);
$('a[onclick^="remove_item"]').toggleClass('d-none', !is_draft);
.prop('disabled', !is_editable);
$('#shipping_tracking_number').prop('disabled', !(is_editable || can_fulfill));
$('.item_sku, .item_qty, .item_price, .item_warehouse').prop('disabled', !is_editable);
$('a[onclick^="remove_item"]').toggleClass('d-none', !is_editable);
// Pending hint
if (is_pending) {
$('#page_subtitle').text('Assign a warehouse for each item, then save to move this order to Draft.');
}
// Action buttons
set_btn_state('#btn_save', is_draft, 'Only draft orders can be edited');
+3 -1
View File
@@ -104,6 +104,7 @@
<div class="col-lg-3">
<select id="filter_status" class="form-select">
<option value="">All Status</option>
<option value="-2">Pending</option>
<option value="-1">Cancelled</option>
<option value="0">Draft</option>
<option value="1">Confirmed</option>
@@ -169,13 +170,14 @@
function order_status_badge(status) {
status = parseInt(status);
const map = {
'-2': '<span class="badge bg-danger">Pending</span>',
'-1': '<span class="badge bg-secondary">Cancelled</span>',
'0': '<span class="badge bg-warning text-dark">Draft</span>',
'1': '<span class="badge bg-info text-white">Confirmed</span>',
'2': '<span class="badge bg-primary">Processing</span>',
'3': '<span class="badge bg-success">Completed</span>',
};
return map[status] ?? '<span class="badge bg-light text-dark">—</span>';
return map[String(status)] ?? '<span class="badge bg-light text-dark">—</span>';
}
function payment_status_badge(status) {
@@ -0,0 +1,87 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
require '../../../assets/utils/classes/OrderManager.php';
$quotation_id = (int)($data['quotation_id'] ?? 0);
if (!$quotation_id) {
exit(json_encode(['success' => 0, 'message' => 'Quotation ID required']));
}
// Load quotation — must be Accepted (status=2)
$sth = $pdo2->prepare(
"SELECT q.*, c.contact_name
FROM ac_quotation q
LEFT JOIN md_contact c ON c.id = q.contact_id
WHERE q.id = :id AND q.company_id = :cid AND q.status = 2
LIMIT 1"
);
$sth->execute([':id' => $quotation_id, ':cid' => $company_id]);
$q = $sth->fetch(PDO::FETCH_ASSOC);
if (!$q) {
exit(json_encode(['success' => 0, 'message' => 'Quotation not found or not in Accepted status']));
}
// Check not already converted
if ((int)$q['order_id'] > 0) {
exit(json_encode(['success' => 0, 'message' => 'Quotation has already been converted to an order']));
}
// Map quotation items → order items (warehouse_id left 0 = to be filled by WMS)
$qt_items = json_decode($q['items'] ?? '[]', true) ?: [];
$ord_items = [];
foreach ($qt_items as $i => $qi) {
$ord_items[] = [
'item_id' => $i,
'product_sku' => $qi['product_sku'] ?? '',
'product_name' => $qi['description'] ?? '',
'warehouse_id' => 0,
'zone' => '',
'aisle' => '',
'rack' => '',
'lot_number' => '',
'serial_number' => '',
'quantity' => (float)($qi['quantity'] ?? 0),
'price' => (float)($qi['unit_price'] ?? 0),
'total_price' => (float)($qi['total_price'] ?? 0),
'stock_out_id' => 0,
];
}
$order_data = [
'quotation_id' => $quotation_id,
'contact_id' => (int)$q['contact_id'],
'order_date' => date('Y-m-d'),
'items' => $ord_items,
'discount' => (float)$q['discount'],
'tax' => (float)$q['tax'],
'shipping_fee' => 0,
'shipping_tracking_number' => '',
'notes' => $q['notes'] ?? '',
];
try {
$new_order_id = null;
dbTransaction($pdo2, function($pdo) use ($order_data, $company_id, $logging, $quotation_id, &$new_order_id) {
$order = new OrderManager($pdo, $company_id);
$new_order_id = $order->saveOrder($order_data, $logging);
// Mark quotation as Converted and link order_id
$pdo->prepare(
"UPDATE ac_quotation SET status = 5, order_id = :oid
WHERE id = :id AND company_id = :cid"
)->execute([':oid' => $new_order_id, ':id' => $quotation_id, ':cid' => $company_id]);
});
$answer['success'] = 1;
$answer['message'] = 'Order created successfully';
$answer['order_id'] = $new_order_id;
} catch (Exception $e) {
$answer['message'] = $e->getMessage();
http_response_code(400);
}
exit(json_encode($answer));
+107
View File
@@ -0,0 +1,107 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$action = $data['action'] ?? '';
$id = (int)($data['id'] ?? 0);
// ── Generate quotation number ─────────────────────────────────────────────────
function generate_quotation_number(PDO $pdo, int $cid): string {
$prefix = 'QT-' . date('Ym') . '-';
$sql = "SELECT quotation_number FROM ac_quotation
WHERE company_id = :cid AND quotation_number LIKE :prefix
ORDER BY id DESC LIMIT 1";
$sth = $pdo->prepare($sql);
$sth->execute([':cid' => $cid, ':prefix' => $prefix . '%']);
$last = $sth->fetchColumn();
$seq = $last ? ((int)substr($last, -4) + 1) : 1;
return $prefix . str_pad($seq, 4, '0', STR_PAD_LEFT);
}
// ── Compute totals from items ─────────────────────────────────────────────────
function compute_totals(array $items, float $discount, float $tax): array {
$subtotal = array_sum(array_map(fn($i) => (float)($i['total_price'] ?? 0), $items));
$grand = $subtotal - $discount + $tax;
return [round($subtotal, 4), round(max(0, $grand), 4)];
}
if ($action === 'create') {
$items = json_decode($data['items'] ?? '[]', true) ?: [];
$discount = (float)($data['discount'] ?? 0);
$tax = (float)($data['tax'] ?? 0);
[$subtotal, $grand] = compute_totals($items, $discount, $tax);
$qnum = generate_quotation_number($pdo2, $company_id);
$sql = "INSERT INTO ac_quotation
(company_id, uuid, quotation_number, contact_id, quotation_date, valid_until,
status, subtotal, discount, tax, grand_total, items, notes, log, created_at)
VALUES
(:cid, :uuid, :qnum, :contact_id, :qdate, :valid_until,
0, :sub, :disc, :tax, :grand, :items, :notes, :log, NOW())";
$sth = $pdo2->prepare($sql);
$sth->execute([
':cid' => $company_id,
':uuid' => $uuid,
':qnum' => $qnum,
':contact_id' => (int)($data['contact_id'] ?? 0),
':qdate' => $data['quotation_date'] ?: null,
':valid_until'=> $data['valid_until'] ?: null,
':sub' => $subtotal,
':disc' => $discount,
':tax' => $tax,
':grand' => $grand,
':items' => json_encode($items),
':notes' => $data['notes'] ?? '',
':log' => json_encode($logging),
]);
$answer['success'] = 1;
$answer['message'] = 'Quotation created';
$answer['new_id'] = (int)$pdo2->lastInsertId();
$answer['quotation_number'] = $qnum;
} elseif ($action === 'update') {
if (!$id) { exit(json_encode(['success' => 0, 'message' => 'ID required'])); }
$items = json_decode($data['items'] ?? '[]', true) ?: [];
$discount = (float)($data['discount'] ?? 0);
$tax = (float)($data['tax'] ?? 0);
[$subtotal, $grand] = compute_totals($items, $discount, $tax);
$sql = "UPDATE ac_quotation SET
contact_id = :contact_id,
quotation_date = :qdate,
valid_until = :valid_until,
subtotal = :sub,
discount = :disc,
tax = :tax,
grand_total = :grand,
items = :items,
notes = :notes
WHERE id = :id AND company_id = :cid AND status = 0";
$sth = $pdo2->prepare($sql);
$sth->execute([
':contact_id' => (int)($data['contact_id'] ?? 0),
':qdate' => $data['quotation_date'] ?: null,
':valid_until' => $data['valid_until'] ?: null,
':sub' => $subtotal,
':disc' => $discount,
':tax' => $tax,
':grand' => $grand,
':items' => json_encode($items),
':notes' => $data['notes'] ?? '',
':id' => $id,
':cid' => $company_id,
]);
$answer['success'] = 1;
$answer['message'] = 'Quotation updated';
} else {
$answer['message'] = 'Unknown action';
}
exit(json_encode($answer));
@@ -0,0 +1,19 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$sql = "SELECT
COUNT(*) AS total,
SUM(status = 0) AS draft,
SUM(status = 1) AS sent,
SUM(status = 2) AS accepted,
SUM(status = 3) AS rejected
FROM ac_quotation
WHERE company_id = :cid";
$sth = $pdo2->prepare($sql);
$sth->execute([':cid' => $company_id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
$answer['success'] = 1;
$answer['output'] = $row;
exit(json_encode($answer));
@@ -0,0 +1,33 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$id = (int)($data['id'] ?? 0);
if ($id) {
// Single quotation
$sql = "SELECT q.*, c.contact_name
FROM ac_quotation q
LEFT JOIN md_contact c ON c.id = q.contact_id
WHERE q.id = :id AND q.company_id = :cid
LIMIT 1";
$sth = $pdo2->prepare($sql);
$sth->execute([':id' => $id, ':cid' => $company_id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if (!$row) { exit(json_encode(['success' => 0, 'message' => 'Not found'])); }
$row['items'] = json_decode($row['items'] ?? '[]', true);
$answer['output'] = $row;
} else {
// List
$sql = "SELECT q.*, c.contact_name
FROM ac_quotation q
LEFT JOIN md_contact c ON c.id = q.contact_id
WHERE q.company_id = :cid
ORDER BY q.id DESC";
$sth = $pdo2->prepare($sql);
$sth->execute([':cid' => $company_id]);
$answer['output'] = $sth->fetchAll(PDO::FETCH_ASSOC);
}
$answer['success'] = 1;
exit(json_encode($answer));
@@ -0,0 +1,42 @@
<?php
session_start();
require '../../../assets/utils/db_auth.php';
$id = (int)($data['id'] ?? 0);
$action = $data['action_type'] ?? '';
if (!$id || !$action) {
exit(json_encode(['success' => 0, 'message' => 'ID and action required']));
}
// Load current status
$sth = $pdo2->prepare("SELECT status FROM ac_quotation WHERE id = :id AND company_id = :cid LIMIT 1");
$sth->execute([':id' => $id, ':cid' => $company_id]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if (!$row) { exit(json_encode(['success' => 0, 'message' => 'Not found'])); }
$cur = (int)$row['status'];
$transitions = [
'send' => ['from' => [0], 'to' => 1, 'label' => 'Sent'],
'accept' => ['from' => [1], 'to' => 2, 'label' => 'Accepted'],
'reject' => ['from' => [1], 'to' => 3, 'label' => 'Rejected'],
'reopen' => ['from' => [1,2,3],'to' => 0, 'label' => 'Draft'],
'cancel' => ['from' => [0,1], 'to' => -1,'label' => 'Cancelled'],
];
if (!isset($transitions[$action])) {
exit(json_encode(['success' => 0, 'message' => 'Invalid action']));
}
$t = $transitions[$action];
if (!in_array($cur, $t['from'], true)) {
exit(json_encode(['success' => 0, 'message' => 'Transition not allowed from current status']));
}
$sth = $pdo2->prepare("UPDATE ac_quotation SET status = :status WHERE id = :id AND company_id = :cid");
$sth->execute([':status' => $t['to'], ':id' => $id, ':cid' => $company_id]);
$answer['success'] = 1;
$answer['message'] = 'Status updated to ' . $t['label'];
exit(json_encode($answer));
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Sales Invoices</h1>
<p class="mb-0 text-muted">Sales invoice management</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-file-invoice fs-1 mb-3 d-block"></i>
<p class="mb-0">Sales Invoices coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+500
View File
@@ -0,0 +1,500 @@
<?php
session_start();
require '../config.php';
require '../include_header.php';
$quotation_id = (int)($_GET['id'] ?? 0);
?>
<body>
<?php require '../include_topbar.php'; ?>
<?php require '../include_sidebar_ac.php'; ?>
<main id="content" class="content py-15">
<div class="container-fluid">
<!-- Page header -->
<div class="row">
<div class="col-12">
<div class="mb-6 d-flex justify-content-between align-items-center">
<div>
<h1 class="fs-3 mb-1" id="page_title">
<?php echo $quotation_id ? 'Quotation Detail' : 'New Quotation'; ?>
</h1>
<p class="mb-0 text-muted" id="page_subtitle">
<?php echo $quotation_id ? '' : 'Create a new sales quotation'; ?>
</p>
</div>
<a href="<?php echo $server_url?>revenue/quotation.php" class="btn btn-light">
<i class="ti ti-arrow-left me-1"></i>Back
</a>
</div>
</div>
</div>
<div class="row g-5">
<!-- Left: Quotation form -->
<div class="col-lg-8">
<!-- Header card -->
<div class="card mb-5">
<div class="card-body p-5">
<h2 class="fs-5 mb-4">Quotation Information</h2>
<div class="row gx-5">
<div class="mb-3 col-lg-6">
<label class="form-label">Contact</label>
<input type="text" id="contact" class="form-control"
placeholder="Search contact..." autocomplete="off">
<input type="hidden" id="contact_id">
</div>
<div class="mb-3 col-lg-6">
<label class="form-label">Quotation Date</label>
<input type="text" id="quotation_date" class="form-control"
placeholder="DD/MM/YYYY" autocomplete="off">
</div>
<div class="mb-3 col-lg-6">
<label class="form-label">Valid Until</label>
<input type="text" id="valid_until" class="form-control"
placeholder="DD/MM/YYYY (optional)" autocomplete="off">
</div>
<div class="mb-3 col-lg-12">
<label class="form-label">Notes / Terms</label>
<textarea id="notes" class="form-control" rows="2"
placeholder="Payment terms, delivery conditions, notes..."></textarea>
</div>
</div>
</div>
</div>
<!-- Items card -->
<div class="card mb-5">
<div class="card-body p-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fs-5 mb-0">Line Items</h2>
<button class="btn btn-sm btn-outline-primary" id="btn_add_item"
onclick="add_item_row()">
<i class="ti ti-plus me-1"></i>Add Line
</button>
</div>
<div class="table-responsive">
<table class="table mb-0" id="item_table">
<thead class="table-light">
<tr>
<th style="width:120px;">SKU</th>
<th>Description</th>
<th style="width:90px;">Qty</th>
<th style="width:120px;">Unit Price</th>
<th style="width:120px;">Total</th>
<th style="width:40px;"></th>
</tr>
</thead>
<tbody id="item_tbody"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Right: Status + Summary + Actions -->
<div class="col-lg-4">
<!-- Status card (existing quotation) -->
<div class="card mb-5 d-none" id="status_card">
<div class="card-body p-4">
<h2 class="fs-5 mb-3">Status</h2>
<div id="badge_status" class="mb-2"></div>
<p class="mb-0 small fw-semibold text-muted" id="quotation_number_display"></p>
</div>
</div>
<!-- Totals -->
<div class="card mb-5">
<div class="card-body p-4">
<h2 class="fs-5 mb-3">Summary</h2>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal</span>
<span id="summary_subtotal">0.00</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">Discount</span>
<input type="number" id="discount" class="form-control form-control-sm text-end"
style="width:110px;" value="0" min="0" step="any" oninput="recalc_totals()">
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">Tax (VAT)</span>
<input type="number" id="tax" class="form-control form-control-sm text-end"
style="width:110px;" value="0" min="0" step="any" oninput="recalc_totals()">
</div>
<hr>
<div class="d-flex justify-content-between fw-bold fs-5">
<span>Grand Total</span>
<span id="summary_grand_total">0.00</span>
</div>
</div>
</div>
<!-- Actions -->
<div class="card">
<div class="card-body p-4 d-flex flex-column gap-2">
<button class="btn btn-primary w-100" id="btn_save" onclick="save_quotation()">
<i class="ti ti-device-floppy me-1"></i>Save Draft
</button>
<button class="btn btn-info text-white w-100 d-none" id="btn_send"
onclick="change_status('send')">
<i class="ti ti-send me-1"></i>Mark as Sent
</button>
<button class="btn btn-success w-100 d-none" id="btn_accept"
onclick="change_status('accept')">
<i class="ti ti-circle-check me-1"></i>Mark as Accepted
</button>
<button class="btn btn-outline-danger w-100 d-none" id="btn_reject"
onclick="change_status('reject')">
<i class="ti ti-x me-1"></i>Mark as Rejected
</button>
<button class="btn btn-outline-secondary w-100 d-none" id="btn_reopen"
onclick="change_status('reopen')">
<i class="ti ti-refresh me-1"></i>Reopen as Draft
</button>
<button class="btn btn-outline-danger w-100 d-none" id="btn_cancel"
onclick="change_status('cancel')">
<i class="ti ti-ban me-1"></i>Cancel Quotation
</button>
<hr class="d-none" id="hr_convert">
<a href="javascript:void(0);" class="btn btn-success w-100 d-none" id="btn_convert"
onclick="convert_to_order()">
<i class="ti ti-transfer me-1"></i>Convert to Order
</a>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var quotation_id = <?php echo $quotation_id; ?>;
var quotation_data = null;
var item_index = 0;
var status_badge = {
'-1': '<span class="badge bg-secondary">Cancelled</span>',
'0': '<span class="badge bg-warning text-dark">Draft</span>',
'1': '<span class="badge bg-info text-white">Sent</span>',
'2': '<span class="badge bg-success">Accepted</span>',
'3': '<span class="badge bg-danger">Rejected</span>',
'5': '<span class="badge bg-primary">Converted</span>',
};
// ── Recalculate totals ────────────────────────────────────────────────────
function recalc_totals() {
var subtotal = 0;
$('#item_tbody tr').each(function() {
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
var price = parseFloat($(this).find('.item_price').val()) || 0;
var total = qty * price;
$(this).find('.item_total').text(format_number(total, 2));
subtotal += total;
});
var discount = parseFloat($('#discount').val()) || 0;
var tax = parseFloat($('#tax').val()) || 0;
var grand = subtotal - discount + tax;
$('#summary_subtotal').text(format_number(subtotal, 2));
$('#summary_grand_total').text(format_number(grand, 2));
}
// ── Add item row ──────────────────────────────────────────────────────────
function add_item_row(item) {
item = item || {};
var idx = item_index++;
var row = `<tr id="item_row_${idx}">
<td>
<input type="text" class="form-control form-control-sm item_sku"
placeholder="SKU..." value="${escape_html(item.product_sku || '')}" autocomplete="off">
</td>
<td>
<input type="text" class="form-control form-control-sm item_desc"
placeholder="Description..." value="${escape_html(item.description || '')}" autocomplete="off">
</td>
<td>
<input type="number" class="form-control form-control-sm item_qty"
value="${item.quantity || 1}" min="0.0001" step="any" oninput="recalc_totals()">
</td>
<td>
<input type="number" class="form-control form-control-sm item_price"
value="${item.unit_price || 0}" min="0" step="any" oninput="recalc_totals()">
</td>
<td class="item_total text-end align-middle">
${format_number((item.quantity || 1) * (item.unit_price || 0), 2)}
</td>
<td class="align-middle">
<a href="javascript:void(0);" class="link-danger remove_item_btn" onclick="remove_item(${idx})">
<i class="ti ti-trash fs-5"></i>
</a>
</td>
</tr>`;
$('#item_tbody').append(row);
recalc_totals();
}
function remove_item(idx) {
$(`#item_row_${idx}`).remove();
recalc_totals();
}
// ── Collect items from DOM ────────────────────────────────────────────────
function collect_items() {
var items = [];
$('#item_tbody tr').each(function() {
var sku = $(this).find('.item_sku').val().trim();
var desc = $(this).find('.item_desc').val().trim();
var qty = parseFloat($(this).find('.item_qty').val()) || 0;
var price = parseFloat($(this).find('.item_price').val()) || 0;
if (!desc && !sku && qty === 0) return;
items.push({
product_sku: sku,
description: desc,
quantity: qty,
unit_price: price,
total_price: parseFloat((qty * price).toFixed(4)),
});
});
return items;
}
// ── Set UI mode based on status ───────────────────────────────────────────
function set_mode(status) {
status = parseInt(status);
var is_draft = status === 0;
var is_sent = status === 1;
var is_accepted = status === 2;
var is_converted = status === 5;
var is_closed = status >= 2 || status === -1;
var is_editable = is_draft;
// Edit controls
$('#contact, #quotation_date, #valid_until, #notes, #discount, #tax, #btn_add_item')
.prop('disabled', !is_editable);
$('.item_sku, .item_desc, .item_qty, .item_price').prop('disabled', !is_editable);
$('.remove_item_btn').toggleClass('d-none', !is_editable);
// Action buttons
$('#btn_save').toggleClass('d-none', !is_draft);
$('#btn_send').toggleClass('d-none', !is_draft);
$('#btn_accept').toggleClass('d-none', !is_sent);
$('#btn_reject').toggleClass('d-none', !is_sent);
$('#btn_reopen').toggleClass('d-none', is_draft || is_closed);
$('#btn_cancel').toggleClass('d-none', is_closed || (is_draft && !quotation_id));
// Convert to Order — only on Accepted, not yet converted
var show_convert = is_accepted && !is_converted;
$('#hr_convert').toggleClass('d-none', !show_convert);
$('#btn_convert').toggleClass('d-none', !show_convert);
}
// ── Load existing quotation ───────────────────────────────────────────────
function retrieve_quotation() {
if (!quotation_id) return Promise.resolve();
return ajax_request({
url: '<?php echo $server_url?>revenue/api/engine/retrieve_quotation.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
data: { id: quotation_id },
onSuccess: function(res) {
quotation_data = res.output;
var q = quotation_data;
$('#page_title').text(q.quotation_number);
$('#page_subtitle').text('');
$('#status_card').removeClass('d-none');
$('#badge_status').html(status_badge[String(q.status)] || q.status);
$('#quotation_number_display').text(q.quotation_number);
$('#contact').val(q.contact_name || '');
$('#contact_id').val(q.contact_id || 0);
$('#quotation_date').val(q.quotation_date ? format_date(q.quotation_date) : '');
$('#valid_until').val(q.valid_until ? format_date(q.valid_until) : '');
$('#notes').val(q.notes || '');
$('#discount').val(parseFloat(q.discount) || 0);
$('#tax').val(parseFloat(q.tax) || 0);
$('#item_tbody').empty();
item_index = 0;
(q.items || []).forEach(function(item) { add_item_row(item); });
recalc_totals();
set_mode(q.status);
}
});
}
// ── Save draft ────────────────────────────────────────────────────────────
function save_quotation() {
var items = collect_items();
if (!items.length) {
bootbox.alert('Please add at least one line item.');
return;
}
ajax_request({
url: '<?php echo $server_url?>revenue/api/engine/manage_quotation.php',
autoPrepare: true,
checkRequired: 0,
action: quotation_id ? 'update' : 'create',
data: {
id: quotation_id,
contact_id: $('#contact_id').val() || 0,
quotation_date: to_iso_date($('#quotation_date').val()),
valid_until: to_iso_date($('#valid_until').val()),
items: JSON.stringify(items),
discount: $('#discount').val() || 0,
tax: $('#tax').val() || 0,
notes: $('#notes').val(),
},
onSuccess: function(res) {
if (!quotation_id && res.new_id) {
window.location.href = '<?php echo $server_url?>revenue/manage_quotation.php?id=' + res.new_id;
} else {
retrieve_quotation();
}
}
});
}
// ── Change status ─────────────────────────────────────────────────────────
function change_status(action_type) {
var labels = {
send: 'Mark this quotation as Sent to customer?',
accept: 'Mark this quotation as Accepted?',
reject: 'Mark this quotation as Rejected?',
reopen: 'Reopen this quotation as Draft?',
cancel: 'Cancel this quotation?',
};
bootbox.confirm({
message: labels[action_type] || 'Confirm?',
buttons: {
confirm: { label: 'Confirm', className: 'btn-primary' },
cancel: { label: 'Back', className: 'btn-secondary' }
},
callback: function(result) {
if (!result) return;
ajax_request({
url: '<?php echo $server_url?>revenue/api/engine/update_quotation_status.php',
autoPrepare: true,
checkRequired: 0,
action: 'update',
data: { id: quotation_id, action_type: action_type },
onSuccess: function() {
retrieve_quotation();
}
});
}
});
}
// ── Convert to Order ─────────────────────────────────────────────────────
function convert_to_order() {
bootbox.confirm({
message: 'Convert this quotation to a Pending sales order? WMS staff will assign warehouse locations before confirming.',
buttons: {
confirm: { label: 'Convert to Order', className: 'btn-success' },
cancel: { label: 'Back', className: 'btn-secondary' }
},
callback: function(result) {
if (!result) return;
ajax_request({
url: '<?php echo $server_url?>revenue/api/engine/convert_quotation.php',
autoPrepare: true,
checkRequired: 0,
action: 'create',
data: { quotation_id: quotation_id },
onSuccess: function(res) {
retrieve_quotation(); // reload — status now shows Converted
}
});
}
});
}
// ── Contact autocomplete ──────────────────────────────────────────────────
function init_contact_search() {
$('#contact').autocomplete({
source: function(req, res) {
ajax_request({
url: '<?php echo $server_url?>contact/api/engine/contact.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
data: { keyword: req.term },
onSuccess: function(data) {
res((data.output || []).map(c => ({
label: c.contact_name,
value: c.contact_name,
id: c.id,
})));
}
});
},
select: function(e, ui) {
$('#contact_id').val(ui.item.id);
}
});
}
// ── Boot ──────────────────────────────────────────────────────────────────
$(async function() {
try {
if (quotation_id) {
await retrieve_quotation();
} else {
var today = flatpickr.formatDate(new Date(), 'd/m/Y');
$('#quotation_date').val(today);
add_item_row();
set_mode(0);
}
flatpickr('#quotation_date', { dateFormat: 'd/m/Y', allowInput: true });
flatpickr('#valid_until', { dateFormat: 'd/m/Y', allowInput: true });
init_contact_search();
} catch(e) { console.log(e); }
});
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Sales Orders</h1>
<p class="mb-0 text-muted">Sales order management</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-shopping-cart fs-1 mb-3 d-block"></i>
<p class="mb-0">Sales Orders coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
+235
View File
@@ -0,0 +1,235 @@
<?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">
<!-- Page header -->
<div class="row">
<div class="col-12">
<div class="mb-6 d-flex justify-content-between align-items-center">
<div>
<h1 class="fs-3 mb-1">Quotations</h1>
<p class="mb-0 text-muted">Sales quotations sent to customers</p>
</div>
<a href="<?php echo $server_url?>revenue/manage_quotation.php" class="btn btn-primary">
<i class="ti ti-plus me-1"></i>New Quotation
</a>
</div>
</div>
</div>
<!-- Stat cards -->
<div class="row g-5 mb-5">
<div class="col-lg-3 col-sm-6 col-12">
<div class="card p-4">
<div class="d-flex align-items-center gap-3">
<div class="icon-shape icon-md bg-primary bg-opacity-10 text-primary rounded-2">
<i class="ti ti-file-description fs-4"></i>
</div>
<div>
<p class="mb-0 text-muted small">Total</p>
<h3 class="fw-bold mb-0" id="stat_total">—</h3>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6 col-12">
<div class="card p-4">
<div class="d-flex align-items-center gap-3">
<div class="icon-shape icon-md bg-warning bg-opacity-10 text-warning rounded-2">
<i class="ti ti-pencil fs-4"></i>
</div>
<div>
<p class="mb-0 text-muted small">Draft</p>
<h3 class="fw-bold mb-0" id="stat_draft">—</h3>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6 col-12">
<div class="card p-4">
<div class="d-flex align-items-center gap-3">
<div class="icon-shape icon-md bg-info bg-opacity-10 text-info rounded-2">
<i class="ti ti-send fs-4"></i>
</div>
<div>
<p class="mb-0 text-muted small">Sent</p>
<h3 class="fw-bold mb-0" id="stat_sent">—</h3>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6 col-12">
<div class="card p-4">
<div class="d-flex align-items-center gap-3">
<div class="icon-shape icon-md bg-success bg-opacity-10 text-success rounded-2">
<i class="ti ti-circle-check fs-4"></i>
</div>
<div>
<p class="mb-0 text-muted small">Accepted</p>
<h3 class="fw-bold mb-0" id="stat_accepted">—</h3>
</div>
</div>
</div>
</div>
</div>
<!-- Filter + Table -->
<div class="row g-5">
<div class="col-12">
<div class="card">
<div class="card-body p-4">
<div class="row g-3 align-items-end">
<div class="col-lg-6">
<div class="position-relative">
<input type="text" id="search_input" class="form-control ps-9"
placeholder="Search quotation number or contact...">
<span class="position-absolute top-25 ms-4">
<i class="ti ti-search text-muted"></i>
</span>
</div>
</div>
<div class="col-lg-3">
<select id="filter_status" class="form-select">
<option value="">All Status</option>
<option value="0">Draft</option>
<option value="1">Sent</option>
<option value="2">Accepted</option>
<option value="3">Rejected</option>
<option value="5">Converted</option>
<option value="-1">Cancelled</option>
</select>
</div>
<div class="col-lg-3">
<button class="btn btn-secondary w-100" onclick="retrieve_quotations()">
<i class="ti ti-search me-1"></i>Search
</button>
</div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-body p-5">
<div class="table-responsive">
<table class="table table-hover mb-0 table-centered" id="quotation_list">
<thead class="table-primary border-light">
<tr>
<th>Quotation #</th>
<th>Date</th>
<th>Valid Until</th>
<th>Contact</th>
<th>Items</th>
<th>Grand Total</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody></tbody>
<tfoot></tfoot>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
<script>
var status_badge = {
'-1': '<span class="badge bg-secondary">Cancelled</span>',
'0': '<span class="badge bg-warning text-dark">Draft</span>',
'1': '<span class="badge bg-info text-white">Sent</span>',
'2': '<span class="badge bg-success">Accepted</span>',
'3': '<span class="badge bg-danger">Rejected</span>',
'5': '<span class="badge bg-primary">Converted</span>',
};
function retrieve_quotations() {
return ajax_request({
url: '<?php echo $server_url?>revenue/api/engine/retrieve_quotation.php',
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var all = res.output || [];
var keyword = $('#search_input').val().toLowerCase();
var fil_status = $('#filter_status').val();
var data = all.filter(function(q) {
var match_kw = !keyword ||
(q.quotation_number || '').toLowerCase().includes(keyword) ||
(q.contact_name || '').toLowerCase().includes(keyword);
var match_st = fil_status === '' || String(q.status) === fil_status;
return match_kw && match_st;
});
// Stats from full list (unfiltered)
$('#stat_total').text(all.length);
$('#stat_draft').text(all.filter(q => String(q.status) === '0').length);
$('#stat_sent').text(all.filter(q => String(q.status) === '1').length);
$('#stat_accepted').text(all.filter(q => String(q.status) === '2').length);
alasql('CREATE TABLE IF NOT EXISTS quotation_list');
alasql.tables.quotation_list.data = data;
var page = generate_pagination('quotation_list', data.length);
$('table#quotation_list tfoot').html(page);
if (typeof change_page_quotation_list === 'function') change_page_quotation_list(1);
}
});
}
function change_page_quotation_list(page_num) {
var offset = (page_num - 1) * prop_limit;
var page_data = alasql(`SELECT * FROM quotation_list LIMIT ${prop_limit} OFFSET ${offset}`);
var body = '';
$.each(page_data, function(i, q) {
var items = typeof q.items === 'string' ? JSON.parse(q.items || '[]') : (q.items || []);
body += `<tr>
<td class="py-3 fw-semibold">${escape_html(q.quotation_number)}</td>
<td class="py-3">${format_date(q.quotation_date)}</td>
<td class="py-3">${q.valid_until ? format_date(q.valid_until) : '<span class="text-muted">—</span>'}</td>
<td class="py-3">${escape_html(q.contact_name || '—')}</td>
<td class="py-3">${items.length} item${items.length !== 1 ? 's' : ''}</td>
<td class="py-3 fw-semibold">${format_number(q.grand_total, 2)}</td>
<td class="py-3">${status_badge[String(q.status)] || q.status}</td>
<td class="py-3">
<a href="<?php echo $server_url?>revenue/manage_quotation.php?id=${q.id}" title="View">
<i class="ti ti-eye fs-5"></i>
</a>
</td>
</tr>`;
});
$('table#quotation_list tbody').html(body);
}
$(function() {
retrieve_quotations();
$('#search_input').on('keyup', function(e) {
if (e.key === 'Enter') retrieve_quotations();
});
});
</script>
</body>
</html>
+36
View File
@@ -0,0 +1,36 @@
<?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-6">
<div class="col-12">
<h1 class="fs-3 mb-1">Receipts</h1>
<p class="mb-0 text-muted">Customer receipts</p>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card p-5 text-center text-muted">
<i class="ti ti-cash fs-1 mb-3 d-block"></i>
<p class="mb-0">Receipts coming soon.</p>
</div>
</div>
</div>
</div>
</main>
<?php require '../include_ending.php'; ?>
</body>
</html>
@@ -0,0 +1,4 @@
-- db: wms
ALTER TABLE `user`
ADD COLUMN `app_access` ENUM('all', 'wms', 'accounting') NOT NULL DEFAULT 'wms'
AFTER `license`;
@@ -0,0 +1,87 @@
-- db: wms2
-- ── Chart of Accounts ────────────────────────────────────────────────────────
CREATE TABLE `ac_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`account_code` varchar(20) NOT NULL,
`account_name` varchar(255) NOT NULL,
`account_type` ENUM('asset','liability','equity','revenue','expense') NOT NULL,
`parent_code` varchar(20) DEFAULT NULL,
`is_posting` tinyint(1) NOT NULL DEFAULT 1 COMMENT '0=header only, 1=can post',
`status` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_company_account` (`company_id`, `account_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ── Departments ───────────────────────────────────────────────────────────────
CREATE TABLE `ac_department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`dept_code` varchar(20) NOT NULL,
`dept_name` varchar(255) NOT NULL,
`description` text DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_company_dept` (`company_id`, `dept_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ── Accounting Periods ────────────────────────────────────────────────────────
CREATE TABLE `ac_period` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`period` varchar(7) NOT NULL COMMENT 'YYYY-MM',
`status` ENUM('open','closed','locked') NOT NULL DEFAULT 'open',
`closed_at` datetime DEFAULT NULL,
`locked_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_company_period` (`company_id`, `period`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ── Journal Entry Headers ─────────────────────────────────────────────────────
CREATE TABLE `ac_journal` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`je_number` varchar(50) NOT NULL,
`period` varchar(7) NOT NULL COMMENT 'YYYY-MM',
`je_date` date NOT NULL,
`description` text DEFAULT NULL,
`source_type` varchar(50) DEFAULT NULL COMMENT 'invoice, po, receipt, payment, manual',
`source_id` int(11) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1=posted, 0=draft, -1=voided',
`created_by` int(11) DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uq_company_je` (`company_id`, `je_number`),
KEY `idx_period` (`company_id`, `period`),
KEY `idx_source` (`company_id`, `source_type`, `source_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ── Journal Entry Lines ───────────────────────────────────────────────────────
CREATE TABLE `ac_journal_line` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`journal_id` int(11) NOT NULL,
`account_code` varchar(20) NOT NULL,
`dept_id` int(11) DEFAULT NULL,
`description` text DEFAULT NULL,
`debit` decimal(15,2) NOT NULL DEFAULT 0.00,
`credit` decimal(15,2) NOT NULL DEFAULT 0.00,
PRIMARY KEY (`id`),
KEY `idx_journal` (`journal_id`),
KEY `idx_account` (`company_id`, `account_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
-- ── Period Balance Summary ────────────────────────────────────────────────────
-- Mirrors warehouse_balance: aggregated debit/credit per account+dept+period
CREATE TABLE `ac_balance` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL,
`period` varchar(7) NOT NULL COMMENT 'YYYY-MM',
`account_code` varchar(20) NOT NULL,
`dept_id` int(11) DEFAULT NULL,
`total_debit` decimal(15,2) NOT NULL DEFAULT 0.00,
`total_credit` decimal(15,2) NOT NULL DEFAULT 0.00,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_balance` (`company_id`, `period`, `account_code`, `dept_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
@@ -0,0 +1,39 @@
-- db: wms2
-- Seeds standard parent (header) COA accounts for every existing company.
-- These are grouping-only accounts (is_posting=0); item accounts are added by each company.
INSERT IGNORE INTO `ac_account` (company_id, account_code, account_name, account_type, parent_code, is_posting, status)
SELECT
c.company_id,
a.account_code,
a.account_name,
a.account_type,
NULL,
0,
1
FROM (
SELECT '1000' AS account_code, 'Assets' AS account_name, 'asset' AS account_type UNION ALL
SELECT '1100', 'Current Assets', 'asset' UNION ALL
SELECT '1200', 'Accounts Receivable', 'asset' UNION ALL
SELECT '1300', 'Inventory', 'asset' UNION ALL
SELECT '1400', 'Other Current Assets', 'asset' UNION ALL
SELECT '1500', 'Fixed Assets', 'asset' UNION ALL
SELECT '2000', 'Liabilities', 'liability' UNION ALL
SELECT '2100', 'Accounts Payable', 'liability' UNION ALL
SELECT '2200', 'Output VAT Payable', 'liability' UNION ALL
SELECT '2300', 'Other Current Liabilities', 'liability' UNION ALL
SELECT '3000', 'Equity', 'equity' UNION ALL
SELECT '3100', 'Share Capital', 'equity' UNION ALL
SELECT '3200', 'Retained Earnings', 'equity' UNION ALL
SELECT '4000', 'Revenue', 'revenue' UNION ALL
SELECT '4100', 'Sales Revenue', 'revenue' UNION ALL
SELECT '4200', 'Other Income', 'revenue' UNION ALL
SELECT '5000', 'Cost of Goods Sold', 'expense' UNION ALL
SELECT '6000', 'Operating Expenses', 'expense' UNION ALL
SELECT '6100', 'Selling Expenses', 'expense' UNION ALL
SELECT '6200', 'General & Administrative Expenses', 'expense' UNION ALL
SELECT '7000', 'Financial Expenses', 'expense' UNION ALL
SELECT '8000', 'Tax Expenses', 'expense' UNION ALL
SELECT '9000', 'Other Expenses', 'expense'
) a
CROSS JOIN (SELECT DISTINCT company_id FROM ac_account UNION SELECT company_id FROM ac_period) c;
+390
View File
@@ -0,0 +1,390 @@
-- db: wms2
-- Seed COA demo data from coa.xlsx (359 accounts)
INSERT IGNORE INTO ac_account
(company_id, account_code, account_name, account_type, parent_code, is_posting, status)
SELECT c.company_id, t.code, t.name, t.type, t.parent, t.posting, t.stat
FROM (
SELECT '100' AS code, 'เงินสดและรายการเทียบเท่าเงินสด' AS name, 'asset' AS type, NULL AS parent, 0 AS posting, 1 AS stat UNION ALL
SELECT '1001000', 'เงินสด', 'asset', '100', 1, 1 UNION ALL
SELECT '10010001', 'เงินสด', 'asset', '100', 1, 1 UNION ALL
SELECT '10010012', 'สินทรัพย์', 'asset', '170', 1, 1 UNION ALL
SELECT '1001100', 'เงินฝากระหว่างทาง', 'asset', '100', 1, 1 UNION ALL
SELECT '1001200', 'เงินสดย่อย', 'asset', '100', 1, 1 UNION ALL
SELECT '1001300', 'เงินในกระเป๋าเงินอิเล็กทรอนิคส์', 'asset', '100', 1, 1 UNION ALL
SELECT '1001300-1', 'เงินในกระเป๋าเงินอิเล็กทรอนิคส์', 'asset', '100', 0, 1 UNION ALL
SELECT '101', 'เงินฝากออมทรัพย์', 'asset', '100', 0, 1 UNION ALL
SELECT '1011000', 'เงินฝากออมทรัพย์-ธนาคารกรุงเทพ', 'asset', '101', 1, 1 UNION ALL
SELECT '1012000', 'เงินฝากออมทรัพย์-ธนาคารกสิกรไทย', 'asset', '101', 1, 1 UNION ALL
SELECT '1013000', 'เงินฝากออมทรัพย์-ธนาคารไทยพาณิชย์', 'asset', '101', 1, 1 UNION ALL
SELECT '1014000', 'เงินฝากออมทรัพย์-ธนาคารกรุงไทย', 'asset', '101', 1, 1 UNION ALL
SELECT '1015000', 'เงินฝากออมทรัพย์-ธนาคารกรุงศรีอยุธยา', 'asset', '101', 1, 1 UNION ALL
SELECT '1016000', 'เงินฝากออมทรัพย์-ธนาคารทหารไทย', 'asset', '101', 1, 1 UNION ALL
SELECT '1017000', 'เงินฝากออมทรัพย์-ธนาคารธนชาติ', 'asset', '101', 1, 1 UNION ALL
SELECT '1018000', 'เงินฝากออมทรัพย์-ธนาคารเพื่อการส่งออกและนำเข้าแห่งประเทศไทย', 'asset', '101', 1, 1 UNION ALL
SELECT '102', 'เงินฝากกระแสรายวัน', 'asset', '100', 0, 1 UNION ALL
SELECT '1021000', 'เงินฝากกระแสรายวัน-ธนาคารกรุงเทพ5890104321', 'asset', '102', 1, 1 UNION ALL
SELECT '1022000', 'เงินฝากกระแสรายวัน-ธนาคารกสิกรไทย', 'asset', '102', 1, 1 UNION ALL
SELECT '1023000', 'เงินฝากกระแสรายวัน-ธนาคารไทยพาณิชย์', 'asset', '102', 1, 1 UNION ALL
SELECT '1024000', 'เงินฝากกระแสรายวัน-ธนาคารกรุงไทย3440105921', 'asset', '102', 1, 1 UNION ALL
SELECT '1025000', 'เงินฝากกระแสรายวัน-ธนาคารกรุงศรีอยุธยา', 'asset', '102', 1, 1 UNION ALL
SELECT '1026000', 'เงินฝากกระแสรายวัน-ธนาคารทหารไทย', 'asset', '102', 1, 1 UNION ALL
SELECT '1027000', 'เงินฝากกระแสรายวัน-ธนาคารธนชาติ', 'asset', '102', 1, 1 UNION ALL
SELECT '103', 'เงินฝากประจำ', 'asset', '100', 0, 1 UNION ALL
SELECT '1031000', 'เงินฝากประจำ-ธนาคารกรุงเทพ', 'asset', '103', 1, 1 UNION ALL
SELECT '1032000', 'เงินฝากประจำ-ธนาคารกสิกรไทย', 'asset', '103', 1, 1 UNION ALL
SELECT '1033000', 'เงินฝากประจำ-ธนาคารไทยพาณิชย์', 'asset', '103', 1, 1 UNION ALL
SELECT '1034000', 'เงินฝากประจำ-ธนาคารกรุงไทย', 'asset', '103', 1, 1 UNION ALL
SELECT '1035000', 'เงินฝากประจำ-ธนาคารกรุงศรีอยุธยา', 'asset', '103', 1, 1 UNION ALL
SELECT '1036000', 'เงินฝากประจำ-ธนาคารทหารไทย', 'asset', '103', 1, 1 UNION ALL
SELECT '1037000', 'เงินฝากประจำ-ธนาคารธนชาติ', 'asset', '103', 1, 1 UNION ALL
SELECT '11-12-01-01', 'CSI-T#1CUR.-BBL 147-3-10664-7', 'asset', NULL, 1, 1 UNION ALL
SELECT '110', 'เงินลงทุนชั่วคราว', 'asset', NULL, 0, 1 UNION ALL
SELECT '120', 'ลูกหนี้การค้าและตั๋วเงินรับ', 'asset', NULL, 0, 1 UNION ALL
SELECT '1201000', 'ลูกหนี้การค้าในประเทศ', 'asset', '120', 1, 1 UNION ALL
SELECT '1202000', 'ลูกหนี้การค้าต่างประเทศ', 'asset', '120', 1, 1 UNION ALL
SELECT '1203000', 'ลูกหนี้การค้าอื่นๆ', 'asset', '120', 1, 1 UNION ALL
SELECT '1203100', 'ลูกหนี้รอยกมา', 'asset', '120', 1, 1 UNION ALL
SELECT '1203200', 'เงินสดจากการขายรอนำฝากธนาคาร', 'asset', '120', 1, 1 UNION ALL
SELECT '1204000', 'เช็ครับลงวันที่ล่วงหน้า', 'asset', '120', 1, 1 UNION ALL
SELECT '1204100', 'เช็คเงินฝากระหว่างทาง', 'asset', '120', 1, 1 UNION ALL
SELECT '1204200', 'เช็คคืน', 'asset', '120', 1, 1 UNION ALL
SELECT '1205000', 'เครดิตการ์ด', 'asset', '120', 1, 1 UNION ALL
SELECT '1209000', 'ค่าเผื่อหนี้สงสัยจะสูญ', 'asset', '120', 1, 1 UNION ALL
SELECT '121', 'เงินให้กู้ยืมระยะสั้น', 'asset', NULL, 0, 1 UNION ALL
SELECT '122', 'ลูกหนี้เงินให้กู้ยืมแก่กรรมการและลูกจ้าง', 'asset', '121', 0, 1 UNION ALL
SELECT '1221000', 'ลูกหนี้เงินให้กู้ยืมแก่กรรมการ', 'asset', '122', 1, 1 UNION ALL
SELECT '1222000', 'ลูกหนี้เงินให้กู้ยืมแก่ลูกจ้าง', 'asset', '122', 1, 1 UNION ALL
SELECT '1223000', 'ลูกหนี้เงินให้กู้ยืมแก่บริษัท.....', 'asset', '122', 1, 1 UNION ALL
SELECT '129', 'ลูกหนี้อื่น', 'asset', '120', 0, 1 UNION ALL
SELECT '1291000', 'ลูกหนี้อื่น', 'asset', '129', 1, 1 UNION ALL
SELECT '1292000', 'ลูกหนี้รอยกมา', 'asset', '129', 1, 1 UNION ALL
SELECT '130', 'สินค้าคงเหลือ', 'asset', NULL, 0, 1 UNION ALL
SELECT '1301000', 'สินค้าสำเร็จรูป', 'asset', '130', 1, 1 UNION ALL
SELECT '1302000', 'วัตถุดิบ', 'asset', '130', 1, 1 UNION ALL
SELECT '1303000', 'วัสดุสิ้นเปลือง', 'asset', '130', 1, 1 UNION ALL
SELECT '1304000', 'งานระหว่างทำ', 'asset', '130', 1, 1 UNION ALL
SELECT '1309000', 'ค่าเผื่อสินค้าด้อยค่า', 'asset', '130', 1, 1 UNION ALL
SELECT '140', 'ค่าใช้จ่ายจ่ายล่วงหน้า', 'asset', NULL, 0, 1 UNION ALL
SELECT '1401000', 'ค่าใช้จ่ายจ่ายล่วงหน้า', 'asset', '140', 1, 1 UNION ALL
SELECT '1402000', 'ภาษีเงินได้นิติบุคคลครึ่งปีภงด.51', 'asset', '140', 1, 1 UNION ALL
SELECT '1403000', 'เงินมัดจำจ่ายล่วงหน้า', 'asset', '140', 1, 1 UNION ALL
SELECT '1404000', 'ค่าประกันภัยท/สจ่ายล่วงหน้า', 'asset', '140', 1, 1 UNION ALL
SELECT '141', 'เงินทดรองจ่าย/เงินมัดจำ', 'asset', NULL, 0, 1 UNION ALL
SELECT '1411000', 'เงินทดรองจ่ายพนักงาน', 'asset', '141', 1, 1 UNION ALL
SELECT '1412000', 'เงินทดรองจ่าย-บุคคลภายนอก', 'asset', '141', 1, 1 UNION ALL
SELECT '1413000', 'เงินมัดจำระยะสั้น', 'asset', '141', 1, 1 UNION ALL
SELECT '1414000', 'เงินประกันระยะสั้น', 'asset', '141', 1, 1 UNION ALL
SELECT '142', 'รายได้ค้างรับ', 'asset', NULL, 0, 1 UNION ALL
SELECT '1421000', 'รายได้ค่าขนส่งค้างรับ', 'asset', '142', 1, 1 UNION ALL
SELECT '1422000', 'รายได้ค่าบริการค้างรับ', 'asset', '142', 1, 1 UNION ALL
SELECT '143', 'ลูกหนี้สรรพากร', 'asset', NULL, 0, 1 UNION ALL
SELECT '1431000', 'บัตรภาษี', 'asset', '143', 1, 1 UNION ALL
SELECT '1432000', 'ภาษีซื้อ', 'asset', '143', 1, 1 UNION ALL
SELECT '1433000', 'ภาษีซื้อยังไม่ถึงกำหนด', 'asset', '143', 1, 1 UNION ALL
SELECT '1434000', 'ภาษีเงินได้ถูกหักณที่จ่าย', 'asset', '143', 1, 1 UNION ALL
SELECT '150', 'สินทรัพย์หมุนเวียนอื่น', 'asset', NULL, 0, 1 UNION ALL
SELECT '160', 'เงินลงทุน', 'asset', NULL, 0, 1 UNION ALL
SELECT '1601000', 'พันธบัตร', 'asset', '160', 1, 1 UNION ALL
SELECT '1602000', 'เงินลงทุนในบจก...', 'asset', '160', 1, 1 UNION ALL
SELECT '1609000', 'เงินลงทุนระยะยาวอื่น', 'asset', '160', 1, 1 UNION ALL
SELECT '161', 'เงินให้กู้ยืมระยะยาว', 'asset', NULL, 0, 1 UNION ALL
SELECT '170', 'ที่ดินอาคารและอุปกรณ์', 'asset', NULL, 0, 1 UNION ALL
SELECT '1701000', 'ที่ดิน', 'asset', '170', 1, 1 UNION ALL
SELECT '1702000', 'อาคาร', 'asset', '170', 1, 1 UNION ALL
SELECT '1702100', 'อาคาร-ส่วนปรับปรุ่งเพิ่ม', 'asset', '170', 1, 1 UNION ALL
SELECT '1702200', 'อาคารโรงงาน', 'asset', '170', 1, 1 UNION ALL
SELECT '1703000', 'เครื่องมือเครื่องใช้ในโรงงาน', 'asset', '170', 1, 1 UNION ALL
SELECT '1703100', 'เครื่องจักร', 'asset', '170', 1, 1 UNION ALL
SELECT '1704000', 'เครื่องใช้สำนักงาน', 'asset', '170', 1, 1 UNION ALL
SELECT '1705000', 'คอมพิวเตอร์', 'asset', '170', 1, 1 UNION ALL
SELECT '1706000', 'ยานพาหนะ', 'asset', '170', 1, 1 UNION ALL
SELECT '171', 'ค่าเสื่อมราคาสะสม', 'asset', NULL, 0, 1 UNION ALL
SELECT '1712000', 'ค่าเสื่อมราคาสะสม-อาคาร', 'asset', '171', 1, 1 UNION ALL
SELECT '1712100', 'ค่าเสื่อมราคาสะสม–อาคารส่วนปรับปรุ่งเพิ่ม', 'asset', '171', 1, 1 UNION ALL
SELECT '1712200', 'ค่าเสื่อมราคาสะสม–อาคารโรงงาน', 'asset', '171', 1, 1 UNION ALL
SELECT '1713000', 'ค่าเสื่อมราคาสะสม–เครื่องมือเครื่องใช้ในโรงงาน', 'asset', '171', 1, 1 UNION ALL
SELECT '1713100', 'ค่าเสื่อมราคาสะสม–เครื่องจักร', 'asset', '171', 1, 1
) AS t
CROSS JOIN (SELECT DISTINCT company_id FROM wms.company_map_user) AS c;
INSERT IGNORE INTO ac_account
(company_id, account_code, account_name, account_type, parent_code, is_posting, status)
SELECT c.company_id, t.code, t.name, t.type, t.parent, t.posting, t.stat
FROM (
SELECT '1714000' AS code, 'ค่าเสื่อมราคาสะสม–เครื่องใช้สำนักงาน' AS name, 'asset' AS type, '171' AS parent, 1 AS posting, 1 AS stat UNION ALL
SELECT '1715000', 'ค่าเสื่อมราคาสะสม–ยานพาหนะ', 'asset', '171', 1, 1 UNION ALL
SELECT '1716000', 'ค่าเสื่อมราคาสะสม–คอมพิวเตอร์', 'asset', '171', 1, 1 UNION ALL
SELECT '180', 'สินทรัพย์ไม่มีตัวตน', 'asset', NULL, 0, 1 UNION ALL
SELECT '1801000', 'ซอฟต์แวร์และแอฟพลิเคชั่น', 'asset', '180', 1, 1 UNION ALL
SELECT '1802000', 'ค่าลิขสิทธิ์', 'asset', '180', 1, 1 UNION ALL
SELECT '1803000', 'ค่าความนิยม', 'asset', '180', 1, 1 UNION ALL
SELECT '1804000', 'ค่าสิทธิบัตร', 'asset', '180', 1, 1 UNION ALL
SELECT '181', 'ค่าตัดจ่ายสะสม', 'asset', NULL, 0, 1 UNION ALL
SELECT '1811000', 'ค่าตัดจำหน่ายสะสม–ซอฟต์แวร์และแอฟพลิเคชั่น', 'asset', '181', 1, 1 UNION ALL
SELECT '1812000', 'ค่าตัดจำหน่ายสะสม–ค่าลิขสิทธิ์', 'asset', '181', 1, 1 UNION ALL
SELECT '1813000', 'ค่าตัดจำหน่ายสะสม–ค่าความนิยม', 'asset', '181', 1, 1 UNION ALL
SELECT '1814000', 'ค่าตัดจำหน่ายสะสม–ค่าสิทธิบัตร', 'asset', '181', 1, 1 UNION ALL
SELECT '190', 'สินทรัพย์ไม่หมุนเวียนอื่น', 'asset', NULL, 0, 1 UNION ALL
SELECT '200', 'เงินเบิกเกินบัญชีและเงินกู้ยืมระยะสั้นจากสถาบันการเงิน', 'liability', NULL, 0, 1 UNION ALL
SELECT '2001000', 'เงินเบิกเกินบัญชีธนาคาร', 'liability', '200', 1, 1 UNION ALL
SELECT '2002000', 'เงินกู้ยืมระยะสั้นบัญชีธนาคารและสถาบันการเงิน', 'liability', '200', 1, 1 UNION ALL
SELECT '210', 'เจ้าหนี้การค้าและเจ้าหนี้อื่น', 'liability', NULL, 0, 1 UNION ALL
SELECT '2101000', 'เจ้าหนี้การค้าในประเทศ', 'liability', '210', 1, 1 UNION ALL
SELECT '2102000', 'เจ้าหนี้การค้าต่างประเทศ', 'liability', '210', 1, 1 UNION ALL
SELECT '2103000', 'เจ้าหนี้การค้าอื่นๆ', 'liability', '210', 1, 1 UNION ALL
SELECT '2103100', 'เจ้าหนี้รอยกมา', 'liability', '210', 1, 1 UNION ALL
SELECT '2103200', 'เจ้าหนี้รอเคลีย์ริ่ง', 'liability', '210', 1, 1 UNION ALL
SELECT '2103300', 'เจ้าหนี้สินค้ารับฝากขาย', 'liability', '210', 1, 1 UNION ALL
SELECT '2103400', 'เจ้าหนี้เช่าซื้อบจก...', 'liability', '210', 1, 1 UNION ALL
SELECT '2104000', 'เช็คจ่ายลงวันที่ล่วงหน้า', 'liability', '210', 1, 1 UNION ALL
SELECT '2104100', 'เช็คจ่าย-รับคืน', 'liability', '210', 1, 1 UNION ALL
SELECT '2105000', 'เครดิตการ์ด-ค้างจ่าย', 'liability', '210', 1, 1 UNION ALL
SELECT '212404', 'เงินรับล่วงหน้า', 'liability', '233', 1, 1 UNION ALL
SELECT '220', 'ส่วนของหนี้สินระยะยาวที่ถึงกำหนดชำระภายในหนึ่งปี', 'liability', NULL, 0, 1 UNION ALL
SELECT '2211000', 'เงินกู้ยืมระยะยาว', 'liability', NULL, 0, 1 UNION ALL
SELECT '2211101', 'เงินกู้ยืมระยะยาว-จากกรรมการ', 'liability', '2211000', 0, 1 UNION ALL
SELECT '2211102', 'เงินกู้ยืมระยะยาว-จากกรรมการ', 'liability', '2211101', 1, 1 UNION ALL
SELECT '230', 'เงินกู้ยืมระยะสั้น', 'liability', NULL, 0, 1 UNION ALL
SELECT '2301000', 'เจ้าหนี้เงินกู้ยืมกรรมการ', 'liability', '230', 1, 1 UNION ALL
SELECT '2302000', 'เจ้าหนี้เงินกู้ยืมบริษัทในเครือ', 'liability', '230', 1, 1 UNION ALL
SELECT '231', 'ค่าใช้จ่ายค้างจ่าย', 'liability', NULL, 0, 1 UNION ALL
SELECT '2311000', 'ค่าแรงงานค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311100', 'เงินเดือนค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311200', 'โบนัสและสวัสดิการค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311300', 'ดอกเบี้ยค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311400', 'ค่านายหน้าค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311500', 'ค่าไฟฟ้าค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311600', 'ค่าน้ำประปาค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311700', 'ค่าโทรศัพท์ค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311800', 'ค่าสอบบัญชีค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2311900', 'ค่าบริการทำบัญชีค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2312000', 'เงินประกันสังคมค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2312001', 'เงินประกันสังคมรอนำส่ง', 'liability', '231', 1, 1 UNION ALL
SELECT '2312100', 'ค่าปรึกษากฎหมายค้างจ่าย', 'liability', '231', 1, 1 UNION ALL
SELECT '2319000', 'ค่าใช้จ่ายค้างจ่ายอื่นๆ', 'liability', '231', 1, 1 UNION ALL
SELECT '232', 'ภาษีค้างจ่าย', 'liability', NULL, 0, 1 UNION ALL
SELECT '2320100', 'ภาษีหักณที่จ่ายภงด1', 'liability', '232', 1, 1 UNION ALL
SELECT '2320300', 'ภาษีหักณที่จ่ายภงด3', 'liability', '232', 1, 1 UNION ALL
SELECT '2325300', 'ภาษีหักณที่จ่ายภงด53', 'liability', '232', 1, 1 UNION ALL
SELECT '2325400', 'ภาษีหักณที่จ่ายภงด54', 'liability', '232', 1, 1 UNION ALL
SELECT '2328000', 'ภาษีเงินได้นิติบุคคลค้างจ่ายภงด50', 'liability', '232', 1, 1 UNION ALL
SELECT '2329000', 'ภาษีอื่นค้างจ่าย', 'liability', '232', 1, 1 UNION ALL
SELECT '233', 'รายได้รับล่วงหน้า', 'liability', NULL, 0, 1 UNION ALL
SELECT '2331000', 'รายได้รับล่วงหน้า-ค่าสินค้า', 'liability', '233', 1, 1 UNION ALL
SELECT '2332000', 'รายได้รับล่วงหน้า-ค่าบริการ', 'liability', '233', 1, 1 UNION ALL
SELECT '2333000', 'รายได้รับล่วงหน้า-ค่านายหน้า', 'liability', '233', 1, 1 UNION ALL
SELECT '2334000', 'เงินมัดจำรับล่วงหน้า', 'liability', '233', 1, 1 UNION ALL
SELECT '234', 'เจ้าหนี้กรมสรรพากร', 'liability', NULL, 0, 1 UNION ALL
SELECT '2341000', 'ภาษีขาย', 'liability', '234', 1, 1 UNION ALL
SELECT '2342000', 'ภาษีขายยังไม่ถึงกำหนด', 'liability', '234', 1, 1 UNION ALL
SELECT '2343000', 'เจ้าหนี้กรมสรรพากรภพ30', 'liability', '234', 1, 1 UNION ALL
SELECT '238', 'ประมาณการหนี้สินระยะสั้น', 'liability', NULL, 0, 1 UNION ALL
SELECT '239', 'หนี้สินหมุนเวียนอื่น', 'liability', NULL, 0, 1 UNION ALL
SELECT '2391000', 'เงินสำรองเลี้ยงชีพค้างจ่าย', 'liability', '239', 1, 1 UNION ALL
SELECT '240', 'เงินกู้ยืมระยะยาว', 'liability', NULL, 0, 1 UNION ALL
SELECT '2401000', 'เงินกู้ยืมระยะยาวจากธนาคารกรุงเทพ', 'liability', '240', 1, 1 UNION ALL
SELECT '2401100', 'เงินกู้ยืมระยะยาวจากธนาคารกสิกรไทย', 'liability', '240', 1, 1 UNION ALL
SELECT '2401200', 'เงินกู้ยืมระยะยาวจากธนาคารไทยพาณิชย์', 'liability', '240', 1, 1 UNION ALL
SELECT '2401300', 'เงินกู้ยืมระยะยาวจากธนาคารกรุงไทย', 'liability', '240', 1, 1 UNION ALL
SELECT '2401400', 'เงินกู้ยืมระยะยาวจากธนาคารกรุงศรีอยุธยา', 'liability', '240', 1, 1 UNION ALL
SELECT '2401500', 'เงินกู้ยืมระยะยาวจากธนาคารทหารไทย', 'liability', '240', 1, 1 UNION ALL
SELECT '2401600', 'เงินกู้ยืมระยะยาวจากธนาคารธนชาติ', 'liability', '240', 1, 1 UNION ALL
SELECT '2402000', 'เงินกู้ยืมระยะยาวจากสถาบันการเงิน......', 'liability', '240', 1, 1 UNION ALL
SELECT '2403000', 'เงินกู้ยืมระยะยาวจากบริษัทในเครือ.....', 'liability', '240', 1, 1 UNION ALL
SELECT '241', 'หนี้สินภาษีเงินได้รอการตัดบัญชี', 'liability', NULL, 0, 1 UNION ALL
SELECT '242', 'ภาระผูกพันผลประโยชน์พนักงาน', 'liability', NULL, 0, 1 UNION ALL
SELECT '243', 'ประมาณการหนี้สินระยะยาว', 'liability', NULL, 0, 1 UNION ALL
SELECT '249', 'หนี้สินไม่หมุนเวียนอื่น', 'liability', NULL, 0, 1 UNION ALL
SELECT '301', 'หุ้นสามัญ', 'equity', NULL, 0, 1 UNION ALL
SELECT '3011000', 'หุ้นสามัญ–นายA', 'equity', '301', 1, 1 UNION ALL
SELECT '3011001', 'หุ้นสามัญ–นายB', 'equity', '301', 1, 1 UNION ALL
SELECT '3011002', 'หุ้นสามัญ–นายC', 'equity', '301', 1, 1 UNION ALL
SELECT '301103', 'ทุนหุ้นสามัญ', 'equity', NULL, 1, 1 UNION ALL
SELECT '302', 'หุ้นบุริมสิทธิ', 'equity', NULL, 0, 1 UNION ALL
SELECT '303', 'กำไรสะสม', 'equity', NULL, 0, 1 UNION ALL
SELECT '303100', 'กำไรสะสม', 'equity', '303', 1, 1 UNION ALL
SELECT '3031000', 'กำไรสะสมจัดสรร', 'equity', '303', 1, 1 UNION ALL
SELECT '3032000', 'กำไรสะสมที่ยังไม่ได้จัดสรร', 'equity', '303', 1, 1 UNION ALL
SELECT '390', 'องค์ประกอบอื่นของส่วนของผู้ถือหุ้น', 'equity', NULL, 0, 1 UNION ALL
SELECT '400', 'รายได้จากการขาย', 'revenue', NULL, 0, 1 UNION ALL
SELECT '4001000', 'รายได้จากการขาย', 'revenue', '400', 1, 1 UNION ALL
SELECT '4001004', 'ส่วนต่างราคาสินค้าCNF', 'revenue', NULL, 1, 1 UNION ALL
SELECT '4002000', 'ส่วนลดการขาย', 'revenue', '400', 1, 1 UNION ALL
SELECT '4003000', 'รับคืนสินค้า', 'revenue', '400', 1, 1
) AS t
CROSS JOIN (SELECT DISTINCT company_id FROM wms.company_map_user) AS c;
INSERT IGNORE INTO ac_account
(company_id, account_code, account_name, account_type, parent_code, is_posting, status)
SELECT c.company_id, t.code, t.name, t.type, t.parent, t.posting, t.stat
FROM (
SELECT '410' AS code, 'รายได้จากการบริการ' AS name, 'revenue' AS type, NULL AS parent, 0 AS posting, 1 AS stat UNION ALL
SELECT '4100100', 'รายได้จากการบริการ', 'revenue', '410', 1, 1 UNION ALL
SELECT '4100200', 'รายได้ค่าติดตั้ง', 'revenue', '410', 1, 1 UNION ALL
SELECT '4100300', 'รายได้ค่าซ่อมแซม', 'revenue', '410', 1, 1 UNION ALL
SELECT '4100400', 'รายได้ค่าเช่า', 'revenue', '410', 1, 1 UNION ALL
SELECT '4111006', 'รายได้จากการขาย-ส่งเสริมการขาย', 'revenue', '400', 1, 1 UNION ALL
SELECT '420', 'รายได้อื่น', 'revenue', NULL, 0, 1 UNION ALL
SELECT '4200100', 'ดอกเบี้ยรับ', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200200', 'กำไร(ขาดทุน)จากการจำหน่ายทรัพย์สิน', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200300', 'ขายทรัพย์สินถาวร', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200400', 'ต้นทุนการขายทรัพย์สินถาวร', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200500', 'กำไร(ขาดทุน)จากอัตราแลกเปลี่ยน', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200600', 'รายได้จากการขายสินค้า/บริการให้บริษัทในเครือ', 'revenue', '420', 1, 1 UNION ALL
SELECT '4200700', 'รายได้อื่น', 'revenue', '420', 1, 1 UNION ALL
SELECT '510', 'ต้นทุนขายและหรือต้นทุนการให้บริการ', 'expense', NULL, 0, 1 UNION ALL
SELECT '5101000', 'ต้นทุนสินค้าเพื่อขาย', 'expense', '510', 1, 1 UNION ALL
SELECT '5101100', 'สินค้าสำเร็จรูปคงเหลือต้นงวด', 'expense', '510', 1, 1 UNION ALL
SELECT '5101200', 'สินค้าสำเร็จรูปคงเหลือปลายงวด', 'expense', '510', 1, 1 UNION ALL
SELECT '5101300', 'ซื้อสินค้า', 'expense', '510', 1, 1 UNION ALL
SELECT '5101301', 'ซื้อผลไม้', 'expense', '510', 1, 1 UNION ALL
SELECT '5101400', 'ค่าระวางและค่าขนส่ง', 'expense', '510', 1, 1 UNION ALL
SELECT '5101500', 'ส่วนลดรับ', 'expense', '510', 1, 1 UNION ALL
SELECT '5101600', 'ส่งคืนสินค้า', 'expense', '510', 1, 1 UNION ALL
SELECT '5102000', 'ต้นทุนสินค้าเพื่อบริการ', 'expense', '510', 1, 1 UNION ALL
SELECT '5102100', 'ต้นทุนวัสดุอุปกรณ์ใช้ไป', 'expense', '510', 1, 1 UNION ALL
SELECT '5102200', 'ต้นทุนเงินเดือนและแรงงานทางตรง', 'expense', '510', 1, 1 UNION ALL
SELECT '5102300', 'ต้นทุนแรงงานภายนอก', 'expense', '510', 1, 1 UNION ALL
SELECT '5102400', 'โสหุ้ยการบริการ', 'expense', '510', 1, 1 UNION ALL
SELECT '5103000', 'ซื้อวัตถุดิบ', 'expense', '510', 1, 1 UNION ALL
SELECT '5103100', 'ส่งคืน-วัตถุดิบ', 'expense', '510', 1, 1 UNION ALL
SELECT '5103200', 'ส่วนลดรับ-วัตถุดิบ', 'expense', '510', 1, 1 UNION ALL
SELECT '5104000', 'ซื้อวัสดุสิ้นเปลือง', 'expense', '510', 1, 1 UNION ALL
SELECT '5104100', 'ส่งคืน-วัสดุสิ้นเปลือง', 'expense', '510', 1, 1 UNION ALL
SELECT '5104200', 'ส่วนลดรับ-วัสดุสิ้นเปลือง', 'expense', '510', 1, 1 UNION ALL
SELECT '5105000', 'ต้นทุนแรงงานทางตรง', 'expense', '510', 1, 1 UNION ALL
SELECT '5105100', 'ต้นทุนแรงงานภายนอก', 'expense', '510', 1, 1 UNION ALL
SELECT '5105200', 'โสหุ้ยการผลิตทางตรง', 'expense', '510', 1, 1 UNION ALL
SELECT '5105300', 'โสหุ้ยการผลิตทางอ้อม', 'expense', '510', 1, 1 UNION ALL
SELECT '5105400', 'ปันส่วนต้นทุนค่าเสื่อมราคา', 'expense', '510', 1, 1 UNION ALL
SELECT '5105500', 'ผลต่างต้นทุนมาตรฐาน', 'expense', '510', 1, 1 UNION ALL
SELECT '5105600', 'ผลต่าง-วัตถุดิบ-ปลายงวด-ต้นงวด', 'expense', '510', 1, 1 UNION ALL
SELECT '5105700', 'ผลต่าง-สินค้าคงเหลือ-ปลายงวด-ต้นงวด', 'expense', '510', 1, 1 UNION ALL
SELECT '5105800', 'ผลต่าง-ต้นทุนค่าใช้จ่ายอื่น-ปลายงวด-ต้นงวด', 'expense', '510', 1, 1 UNION ALL
SELECT '5105900', 'ProvidendFund(SI)', 'expense', '510', 1, 1 UNION ALL
SELECT '520', 'ค่าใช้จ่ายในการขาย', 'expense', NULL, 0, 1 UNION ALL
SELECT '5200100', 'ค่าแรงและผลประโยชน์พนักงานขายชั่วคราว', 'expense', '520', 1, 1 UNION ALL
SELECT '5200200', 'ค่านายหน้าและการส่งเสริมการขาย', 'expense', '520', 1, 1 UNION ALL
SELECT '5200300', 'ค่านายหน้าและการส่งเสริมการขายบุคคลภายนอก', 'expense', '520', 1, 1 UNION ALL
SELECT '5200500', 'ค่าโฆษณา', 'expense', '520', 1, 1 UNION ALL
SELECT '5200600', 'ค่ารับรองขาย', 'expense', '520', 1, 1 UNION ALL
SELECT '5200700', 'ค่าตอบแทนขายอื่น', 'expense', '520', 1, 1 UNION ALL
SELECT '521', 'ค่าใช้จ่ายในการบริหาร', 'expense', NULL, 0, 1 UNION ALL
SELECT '5210010', 'ค่าแรง', 'expense', '521', 1, 1 UNION ALL
SELECT '5210020', 'เงินเดือน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210030', 'ค่าล่วงเวลา', 'expense', '521', 1, 1 UNION ALL
SELECT '5210040', 'ค่าสวัสดิการ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210050', 'โบนัส', 'expense', '521', 1, 1 UNION ALL
SELECT '5210060', 'เงินเพิ่มพิเศษ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210070', 'ค่าเดินทางและพักอาศัย', 'expense', '521', 1, 1 UNION ALL
SELECT '5210080', 'คอมมิชชั่น', 'expense', '521', 1, 1 UNION ALL
SELECT '5210090', 'เงินสมทบกองทุนประกันสังคม', 'expense', '521', 1, 1 UNION ALL
SELECT '5210100', 'เงินสมทบกองทุนทดแทน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210110', 'ค่ารักษาพยาบาล', 'expense', '521', 1, 1 UNION ALL
SELECT '5210120', 'ค่าอบรมสัมมนา', 'expense', '521', 1, 1 UNION ALL
SELECT '5210130', 'ค่าแบบฟอร์มพนักงาน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210140', 'ค่าเบี้ยประกันชีวิตและอุบัติเหตุ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210150', 'ค่าอาหารและเครื่องดื่ม', 'expense', '521', 1, 1 UNION ALL
SELECT '5210160', 'ค่าจัดเลี้ยงและสันทนาการ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210170', 'ค่าสวัสดิการอื่นๆ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210180', 'ค่าทำบัญชี', 'expense', '521', 1, 1 UNION ALL
SELECT '5210190', 'ค่าสอบบัญชี', 'expense', '521', 1, 1 UNION ALL
SELECT '5210200', 'ค่าทนายและกฎหมาย', 'expense', '521', 1, 1 UNION ALL
SELECT '5210210', 'ค่าธรรมเนียมวิชาชีพอื่น', 'expense', '521', 1, 1 UNION ALL
SELECT '5210220', 'ค่าธรรมเนียมธนาคาร', 'expense', '521', 1, 1 UNION ALL
SELECT '5210230', 'ค่าธรรมเนียมใบอนุญาต', 'expense', '521', 1, 1 UNION ALL
SELECT '5210240', 'ค่าภาษีโรงเรือน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210250', 'ค่าอากรแสตมป์', 'expense', '521', 1, 1 UNION ALL
SELECT '5210260', 'ค่าภาษียานพาหนะ', 'expense', '521', 1, 1 UNION ALL
SELECT '5210270', 'ภาษีป้าย', 'expense', '521', 1, 1 UNION ALL
SELECT '5210280', 'ค่าธรรมเนียมอื่น', 'expense', '521', 1, 1 UNION ALL
SELECT '5210290', 'ค่าที่ปรึกษา', 'expense', '521', 1, 1 UNION ALL
SELECT '5210300', 'ค่าเบี้ยประกัน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210310', 'ค่าวัสดุและอุปกรณ์สิ้นเปลือง', 'expense', '521', 1, 1 UNION ALL
SELECT '5210320', 'ค่าเครื่องเขียนแบบพิมพ์', 'expense', '521', 1, 1 UNION ALL
SELECT '5210330', 'ค่าซ่อมแซมและบำรุงรักษา', 'expense', '521', 1, 1 UNION ALL
SELECT '5210340', 'วัสดุสิ้นเปลือง', 'expense', '521', 1, 1 UNION ALL
SELECT '5210350', 'ค่าวารสารและสมาชิก', 'expense', '521', 1, 1 UNION ALL
SELECT '5210360', 'ค่าเช่าสำนักงาน', 'expense', '521', 1, 1 UNION ALL
SELECT '5210370', 'ค่ารักษาความปลอดภัย', 'expense', '521', 1, 1 UNION ALL
SELECT '5210380', 'ค่ารักษาความสะอาด', 'expense', '521', 1, 1 UNION ALL
SELECT '5210390', 'ค่าใช้จ่ายส่วนกลาง', 'expense', '521', 1, 1 UNION ALL
SELECT '5210400', 'ค่าใช้จ่ายในการฝึกอบรม', 'expense', '521', 1, 1 UNION ALL
SELECT '5210410', 'ค่าสวัสดิการพนักงาน-ทั่วไป', 'expense', '521', 1, 1 UNION ALL
SELECT '5210420', 'ค่าซ่อมแซมบำรุงรักษา', 'expense', '521', 1, 1 UNION ALL
SELECT '5210430', 'ค่าจ้างและบริการทั่วไป', 'expense', '521', 1, 1 UNION ALL
SELECT '5210440', 'เงินบริจาค-เข้าเกณฑ์', 'expense', '521', 1, 1 UNION ALL
SELECT '5210450', 'ค่าใช้จ่ายเบ็ดเตล็ด', 'expense', '521', 1, 1 UNION ALL
SELECT '5210460', 'ส่วนขาดเกินบัญชี', 'expense', '521', 1, 1 UNION ALL
SELECT '5211500', 'ProvidendFund(AD)', 'expense', '521', 1, 1 UNION ALL
SELECT '522', 'ค่าสาธารณูปโภคและสื่อสาร', 'expense', NULL, 0, 1
) AS t
CROSS JOIN (SELECT DISTINCT company_id FROM wms.company_map_user) AS c;
INSERT IGNORE INTO ac_account
(company_id, account_code, account_name, account_type, parent_code, is_posting, status)
SELECT c.company_id, t.code, t.name, t.type, t.parent, t.posting, t.stat
FROM (
SELECT '5220010' AS code, 'ค่าโทรศัพท์' AS name, 'expense' AS type, '522' AS parent, 1 AS posting, 1 AS stat UNION ALL
SELECT '5220020', 'ค่าไฟฟ้า', 'expense', '522', 1, 1 UNION ALL
SELECT '5220030', 'ค่าน้ำประปา', 'expense', '522', 1, 1 UNION ALL
SELECT '5220040', 'ค่าไปรษณีย์และอากรแสตมป์', 'expense', '522', 1, 1 UNION ALL
SELECT '530', 'ค่าเสื่อมราคา', 'expense', NULL, 0, 1 UNION ALL
SELECT '5301000', 'ค่าเสื่อมราคา-อาคาร', 'expense', '530', 1, 1 UNION ALL
SELECT '5301100', 'ค่าเสื่อมราคา–อาคารส่วนปรับปรุ่งเพิ่ม', 'expense', '530', 1, 1 UNION ALL
SELECT '5301200', 'ค่าเสื่อมราคา–อาคารโรงงาน', 'expense', '530', 1, 1 UNION ALL
SELECT '5301300', 'ค่าเสื่อมราคา–เครื่องมือเครื่องใช้ในโรงงาน', 'expense', '530', 1, 1 UNION ALL
SELECT '5301400', 'ค่าเสื่อมราคา–เครื่องจักร', 'expense', '530', 1, 1 UNION ALL
SELECT '5301500', 'ค่าเสื่อมราคา–เครื่องใช้สำนักงาน', 'expense', '530', 1, 1 UNION ALL
SELECT '5301600', 'ค่าเสื่อมราคา–ยานพาหนะ', 'expense', '530', 1, 1 UNION ALL
SELECT '5301700', 'ค่าเสื่อมราคา–คอมพิวเตอร์', 'expense', '530', 1, 1 UNION ALL
SELECT '531', 'ค่าตัดจ่าย', 'expense', NULL, 0, 1 UNION ALL
SELECT '5310030', 'ค่าเบี้ยเลี้ยง', 'expense', '520', 1, 1 UNION ALL
SELECT '5311000', 'ค่าตัดจำหน่าย–ซอฟต์แวร์และแอฟพลิเคชั่น', 'expense', '531', 1, 1 UNION ALL
SELECT '5311100', 'ค่าตัดจำหน่าย–ค่าลิขสิทธิ์', 'expense', '531', 1, 1 UNION ALL
SELECT '5311200', 'ค่าตัดจำหน่าย–ค่าความนิยม', 'expense', '531', 1, 1 UNION ALL
SELECT '5311300', 'ค่าตัดจำหน่าย–ค่าสิทธิบัตร', 'expense', '531', 1, 1 UNION ALL
SELECT '532', 'ปรับปรุงสำรองและการด้อยค่า', 'expense', NULL, 0, 1 UNION ALL
SELECT '5321000', 'หนี้สูญ', 'expense', '532', 1, 1 UNION ALL
SELECT '5321100', 'ปรับปรุงหนี้สงสัยจะสูญ', 'expense', '532', 1, 1 UNION ALL
SELECT '5321200', 'ปรับปรุงสินค้าด้อยค่า', 'expense', '532', 1, 1 UNION ALL
SELECT '5321300', 'ปรับปรุงการตั้งผลประโยชน์พนักงาน', 'expense', '532', 1, 1 UNION ALL
SELECT '5321400', 'ขาดทุนจากอัตราแลกเปลี่ยน', 'expense', '532', 1, 1 UNION ALL
SELECT '537008', 'ค่าใช้จ่ายในการจัดตั้งบริษัท', 'expense', NULL, 1, 1 UNION ALL
SELECT '541', 'ค่าเบี้ยประกันภัย', 'expense', NULL, 0, 1 UNION ALL
SELECT '5411000', 'ค่าเบี้ยประกัน-อาคาร', 'expense', '541', 1, 1 UNION ALL
SELECT '5411100', 'ค่าเบี้ยประกัน-อุปกรณ์สำนักงาน', 'expense', '541', 1, 1 UNION ALL
SELECT '5411200', 'ค่าเบี้ยประกัน-เครื่องตกแต่งสำนักงาน', 'expense', '541', 1, 1 UNION ALL
SELECT '5411300', 'ค่าเบี้ยประกัน-ยานพาหนะ', 'expense', '541', 1, 1 UNION ALL
SELECT '590', 'ค่าใช้จ่ายอื่นๆ', 'expense', NULL, 0, 1 UNION ALL
SELECT '5901000', 'ดอกเบี้ยจ่าย', 'expense', '590', 1, 1 UNION ALL
SELECT '5901100', 'หนี้สงสัยจะสูญ', 'expense', '590', 1, 1 UNION ALL
SELECT '5901200', 'ค่ารับรอง', 'expense', '590', 1, 1 UNION ALL
SELECT '5901300', 'ค่าบริจาคการกุศล', 'expense', '590', 1, 1 UNION ALL
SELECT '5901400', 'ส่วนลดจากการขายลดเช็ค', 'expense', '590', 1, 1 UNION ALL
SELECT '5901500', 'ค่าใช้จ่ายเบ็ดเตล็ด', 'expense', '590', 1, 1 UNION ALL
SELECT '5901600', 'ค่าใช้จ่ายอื่นๆ', 'expense', '590', 1, 1 UNION ALL
SELECT '5901700', 'หนี้สูญ', 'expense', '590', 1, 1 UNION ALL
SELECT '5902000', 'ProvidendFund(Sales)', 'expense', '590', 1, 1 UNION ALL
SELECT '591', 'รายจ่ายที่ไม่ถือเป็นรายจ่ายตามประมวลกฎหมายรัษฎากร', 'expense', NULL, 0, 1 UNION ALL
SELECT '5911000', 'ค่าปรับภาษีอากร', 'expense', '591', 1, 1 UNION ALL
SELECT '5911100', 'ภาษีซื้อไม่ขอคืน', 'expense', '591', 1, 1 UNION ALL
SELECT '5911200', 'หนี้สูญเกินกฎหมายกำหนด', 'expense', '591', 1, 1 UNION ALL
SELECT '5911300', 'เงินบริจาคไม่เข้าเกณฑ์', 'expense', '591', 1, 1 UNION ALL
SELECT '5911400', 'ค่าใช้จ่ายไม่มีหลักฐาน', 'expense', '591', 1, 1 UNION ALL
SELECT '5911500', 'ค่าใช้จ่ายที่ไม่สามารถระบุได้', 'expense', '591', 1, 1 UNION ALL
SELECT '5911600', 'ค่าใช้จ่ายปีก่อน', 'expense', '591', 1, 1 UNION ALL
SELECT '5919999', 'รายจ่ายยังไม่ได้แยกประเภท', 'expense', '591', 1, 1 UNION ALL
SELECT '592', 'ภาษีเงินได้นิติบุคคลภงด50', 'expense', NULL, 0, 1 UNION ALL
SELECT '5921000', 'ภาษีเงินได้นิติบุคคลภงด50', 'expense', '592', 1, 1 UNION ALL
SELECT '900', 'สถิติการดำเนินงาน', 'expense', NULL, 0, 1 UNION ALL
SELECT '9000001', 'ทดสอบ', 'expense', '900', 1, 1 UNION ALL
SELECT '9001000', 'จำนวนลูกค้า', 'expense', '900', 1, 1 UNION ALL
SELECT '9002000', 'ขนาดพื้นที่เช่า', 'expense', '900', 1, 1 UNION ALL
SELECT '9003000', 'น้ำหนักขนส่ง', 'expense', '900', 1, 1 UNION ALL
SELECT '9009999', 'บัญชีเคลียร์ตัวเลขสถิติ', 'expense', '900', 1, 1 UNION ALL
SELECT '9999999', '9999999', 'expense', NULL, 1, 1
) AS t
CROSS JOIN (SELECT DISTINCT company_id FROM wms.company_map_user) AS c;
@@ -0,0 +1,24 @@
-- db: wms2
CREATE TABLE IF NOT EXISTS `ac_quotation` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`company_id` int(11) NOT NULL DEFAULT 0,
`uuid` varchar(32) NOT NULL DEFAULT '',
`quotation_number` varchar(50) NOT NULL DEFAULT '',
`contact_id` int(11) NOT NULL DEFAULT 0,
`quotation_date` date DEFAULT NULL,
`valid_until` date DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 0,
`subtotal` decimal(18,4) NOT NULL DEFAULT 0.0000,
`discount` decimal(18,4) NOT NULL DEFAULT 0.0000,
`tax` decimal(18,4) NOT NULL DEFAULT 0.0000,
`grand_total` decimal(18,4) NOT NULL DEFAULT 0.0000,
`items` mediumtext NOT NULL DEFAULT '[]',
`notes` text NOT NULL DEFAULT '',
`log` mediumtext NOT NULL DEFAULT '{}',
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `company_id` (`company_id`),
KEY `quotation_number` (`quotation_number`),
KEY `status` (`status`),
KEY `contact_id` (`contact_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,7 @@
-- db: wms2
ALTER TABLE td_order
ADD COLUMN quotation_id int(11) NOT NULL DEFAULT 0 AFTER uuid,
ADD KEY quotation_id (quotation_id);
ALTER TABLE ac_quotation
ADD COLUMN order_id int(11) NOT NULL DEFAULT 0 AFTER status;