Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,68 @@
<?php
namespace app\Controllers;
use app\Models\UserModel;
use app\Models\UserLogModel;
class AuthController extends Controller {
public function index() {
// If already logged in, redirect to dashboard
if (isset($_SESSION['user_id'])) {
header('Location: ' . BASE_URL . '/dashboard');
exit;
}
// Render the login view
$this->view('auth/login', [
'title' => 'เข้าสู่ระบบ | ' . APP_NAME
]);
}
public function login() {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($username) || empty($password)) {
$_SESSION['error'] = 'กรุณากรอกข้อมูลให้ครบถ้วน';
header('Location: ' . BASE_URL . '/login');
exit;
}
$userModel = new UserModel();
$user = $userModel->getUserByUsername($username);
if ($user && password_verify($password, $user['password_hash'])) {
// Login success
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['role_id'] = $user['role_id'];
$_SESSION['first_name'] = $user['first_name'];
$_SESSION['last_name'] = $user['last_name'];
$userModel->updateLastLogin($user['id']);
// Record Login Log
$logModel = new UserLogModel();
$logModel->logAction($user['id'], 'LOGIN', 'เข้าสู่ระบบสำเร็จ');
$_SESSION['success'] = 'เข้าสู่ระบบสำเร็จ ยินดีต้อนรับคุณ ' . htmlspecialchars($user['first_name']);
header('Location: ' . BASE_URL . '/dashboard');
exit;
} else {
// Login failed
$_SESSION['error'] = 'ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function logout() {
session_destroy();
session_start(); // Start a new session for the success message
$_SESSION['success'] = 'ออกจากระบบสำเร็จ';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
?>
@@ -0,0 +1,21 @@
<?php
namespace app\Controllers;
abstract class Controller {
/**
* Basic view rendering method
*/
protected function view($viewName, $data = []) {
// Extract data to variables
extract($data);
$viewPath = APP_ROOT . '/app/Views/' . $viewName . '.php';
if (file_exists($viewPath)) {
require_once $viewPath;
} else {
die("View does not exist: " . $viewName);
}
}
}
?>
@@ -0,0 +1,85 @@
<?php
namespace app\Controllers;
use app\Models\PayrollModel;
use app\Models\SystemSettingModel;
use PDO;
class DashboardController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function index() {
$payrollModel = new PayrollModel();
// 1. Get latest payroll period
$periods = $payrollModel->getAllPayrollPeriods();
$latestPeriod = !empty($periods) ? $periods[0] : null;
// 2. Aggregate stats for the latest period
$totalEmployees = 0;
$totalSalary = 0;
if ($latestPeriod) {
$stats = $payrollModel->getPeriodStats($latestPeriod['id']);
$totalEmployees = $stats['total_emp'] ?? 0;
$totalSalary = $stats['total_net'] ?? 0;
}
// 3. For Chart: Get yearly summary
$settingModel = new SystemSettingModel();
$chartHistoryYears = (int)$settingModel->getSetting('chart_history_years', '3');
$baseYear = date('Y');
if ($latestPeriod && !empty($latestPeriod['year_no'])) {
$baseYear = $latestPeriod['year_no'];
}
$availableYears = [];
for ($i = 0; $i < $chartHistoryYears; $i++) {
$availableYears[] = $baseYear - $i;
}
$currentYear = $_GET['year'] ?? $baseYear;
if (!in_array($currentYear, $availableYears)) {
$currentYear = $baseYear;
}
$yearBE = $currentYear + 543;
$yearlyStats = $payrollModel->getYearlySummary($currentYear);
$chartLabels = ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'];
$chartIncome = array_fill(0, 12, 0);
$chartDeduction = array_fill(0, 12, 0);
foreach ($yearlyStats as $monthStr => $data) {
$mIdx = (int)$monthStr - 1; // '01' -> 0, '12' -> 11
if ($mIdx >= 0 && $mIdx < 12) {
$chartIncome[$mIdx] = $data['income'];
$chartDeduction[$mIdx] = $data['deduction'];
}
}
$this->view('dashboard/index', [
'title' => 'Dashboard | ' . APP_NAME,
'activeMenu' => 'dashboard',
'latestPeriod' => $latestPeriod,
'totalEmployees' => $totalEmployees,
'totalSalary' => $totalSalary,
'currentYear' => $currentYear,
'yearBE' => $yearBE,
'availableYears' => $availableYears,
'chartLabels' => json_encode($chartLabels),
'chartIncome' => json_encode($chartIncome),
'chartDeduction' => json_encode($chartDeduction)
]);
}
}
?>
@@ -0,0 +1,45 @@
<?php
namespace app\Controllers;
use app\Models\HrPersonModel;
class HrController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
header('HTTP/1.1 401 Unauthorized');
exit;
}
}
public function image() {
$nationalId = isset($_GET['national_id']) ? $_GET['national_id'] : '';
if (empty($nationalId)) {
$this->outputDefaultImage();
return;
}
$hrModel = new HrPersonModel();
$imageData = $hrModel->getPersonImage($nationalId);
if (!$imageData || empty($imageData['HR_IMAGE'])) {
$this->outputDefaultImage();
return;
}
// Output image
header("Content-Type: image/jpeg");
header("Cache-Control: max-age=86400, public"); // Cache for 1 day
echo $imageData['HR_IMAGE'];
exit;
}
private function outputDefaultImage() {
// Output a 1x1 transparent PNG or an SVG avatar as fallback
header('Content-Type: image/svg+xml');
echo '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" /></svg>';
exit;
}
}
?>
@@ -0,0 +1,476 @@
<?php
namespace app\Controllers;
use app\Models\PayrollModel;
use app\Models\IncomeMasterModel;
use app\Models\DeductionMasterModel;
use app\Models\HrPersonModel;
class ImportController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function index() {
$payrollModel = new PayrollModel();
// Fetch all months for the dropdown
$periods = $payrollModel->getAllPayrollPeriods();
$this->view('import/index', [
'title' => 'นำเข้าข้อมูล | ' . APP_NAME,
'activeMenu' => 'import',
'periods' => $periods
]);
}
public function upload() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/import');
exit;
}
$salaryMonthId = $_POST['salary_month_id'] ?? null;
$fileType = $_POST['file_type'] ?? 'csv';
if (empty($salaryMonthId) || empty($_FILES['csv_file']['tmp_name'])) {
$_SESSION['error'] = 'กรุณาเลือกงวดเงินเดือนและไฟล์';
header('Location: ' . BASE_URL . '/import');
exit;
}
$file = $_FILES['csv_file'];
if ($file['error'] !== UPLOAD_ERR_OK) {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการอัปโหลดไฟล์';
header('Location: ' . BASE_URL . '/import');
exit;
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($fileType === 'cgd_txt' && $ext !== 'txt') {
$_SESSION['error'] = 'ระบบต้องการไฟล์นามสกุล .txt สำหรับรูปแบบกรมบัญชีกลาง';
header('Location: ' . BASE_URL . '/import');
exit;
} elseif ($fileType === 'csv' && $ext !== 'csv') {
$_SESSION['error'] = 'รองรับเฉพาะไฟล์ .csv เท่านั้น';
header('Location: ' . BASE_URL . '/import');
exit;
}
$handle = fopen($file['tmp_name'], 'r');
if (!$handle) {
$_SESSION['error'] = 'ไม่สามารถอ่านไฟล์ได้';
header('Location: ' . BASE_URL . '/import');
exit;
}
$incomeModel = new IncomeMasterModel();
$deductionModel = new DeductionMasterModel();
$payrollModel = new PayrollModel();
$payrollModel->beginTransaction();
try {
if ($fileType === 'cgd_txt') {
$this->processCGDFile($handle, $salaryMonthId, $payrollModel, $incomeModel, $deductionModel, $file['name']);
} else {
$this->processCSVFile($handle, $salaryMonthId, $payrollModel, $incomeModel, $deductionModel, $file['name']);
}
$payrollModel->commit();
fclose($handle);
header('Location: ' . BASE_URL . '/import');
exit;
} catch (\Exception $e) {
$payrollModel->rollBack();
fclose($handle);
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการประมวลผล: ' . $e->getMessage();
header('Location: ' . BASE_URL . '/import');
exit;
}
}
private function getIncomeMap($incomeModel) {
$incomes = $incomeModel->getActiveIncomes();
$map = [];
foreach ($incomes as $inc) {
$map[$inc['code']] = $inc['id'];
}
return $map;
}
private function getDeductionMap($deductionModel) {
$deductions = $deductionModel->getActiveDeductions();
$map = [];
foreach ($deductions as $ded) {
$map[$ded['code']] = $ded['id'];
}
return $map;
}
private function getOrCreateIncomeMaster($code, $name, $incomeModel, &$incomeMap) {
if (isset($incomeMap[$code])) {
return $incomeMap[$code];
}
$incomeModel->createIncome([
'code' => $code,
'name' => $name,
'is_taxable' => 1,
'is_active' => 1,
'display_order' => 99,
'fund_source' => 'CGD'
]);
$incomeMap = $this->getIncomeMap($incomeModel);
return $incomeMap[$code] ?? null;
}
private function getOrCreateDeductionMaster($code, $name, $deductionModel, &$deductionMap) {
if (isset($deductionMap[$code])) {
return $deductionMap[$code];
}
$deductionModel->createDeduction([
'code' => $code,
'name' => $name,
'is_taxable' => 1,
'is_active' => 1,
'display_order' => 99,
'fund_source' => 'CGD'
]);
$deductionMap = $this->getDeductionMap($deductionModel);
return $deductionMap[$code] ?? null;
}
private function parseCGDAmount($amountStr) {
if (empty($amountStr) || strlen($amountStr) < 3) return 0;
return (float) substr_replace($amountStr, ".", -2, 0);
}
private function processCGDFile($handle, $salaryMonthId, $payrollModel, $incomeModel, $deductionModel, $fileName) {
$incomeMap = $this->getIncomeMap($incomeModel);
$deductionMap = $this->getDeductionMap($deductionModel);
$totalRecords = 0;
$successRecords = 0;
$errorRecords = 0;
$profileModel = new \app\Models\EmployeeProfileModel();
$cgdIncomesStatic = [
15 => 'เงินเดือนตกเบิก/ค่าจ้างประจําตกเบิก',
16 => 'เงิน ปตจ.',
17 => 'เงิน ปจต. ตกเบิก',
18 => 'พ.ข.อ./ตกเบิก',
19 => 'พ.ส.ร./ตกเบิก',
20 => 'พ.ค.ว./ตกเบิก',
21 => 'พ.ป.ผ./ตกเบิก',
22 => 'สปพ./ตกเบิก',
23 => 'ตปพ./ตกเบิก',
24 => 'ต.ข.ท.ปจต.',
25 => 'ต.ข.ท.ปจต. ตกเบิก',
26 => 'ต.ข.8-8ว.',
27 => 'ต.ข.8-8ว. ตกเบิก',
28 => 'ต.ด.ข.1-7/ตกเบิก, ต.ด.จ./ตกเบิก',
29 => 'ง.ต.พ.ข./ตกเบิก, ง.ต.พ.จ./ตกเบิก',
30 => 'ค่าเช่าบ้าน/ตกเบิก',
31 => 'ช่วยเหลือบุตร/ตกเบิก',
32 => 'การศึกษาบุตร/ตกเบิก',
33 => 'เงินรางวัล/เงินท้าทาย',
42 => 'เงินเพิ่มอื่นๆ',
];
$cgdDeductionsStatic = [
44 => 'ภาษี/ตกเบิก',
45 => 'เงินกู้เพื่อที่อยู่อาศัย',
46 => 'ค่าหุ้น-เงินกู้สหกรณ์',
47 => 'เงินกู้เพื่อการศึกษา',
48 => 'กบข./ตกเบิก, กสจ./ตกเบิก',
49 => 'กบข.ส่วนเพิ่ม/ตกเบิก',
50 => 'ง.ก.บ.(ธอส.)',
51 => 'ง.ก.บ.(อส.)',
52 => 'ง.ก.ธ.',
53 => 'เงินกู้ ธพ.',
54 => 'ชดใช้ทางแพ่ง',
55 => 'เงินเรียกคืน',
56 => 'ค่าสาธารณูปโภค',
57 => 'เงินสวัสดิการสโมสร',
58 => 'ค่าฌาปนกิจ',
59 => 'งท. สงเคราะห์',
76 => 'เงินลด/หักอื่นๆ',
];
while (($line = fgets($handle)) !== false) {
$line = trim($line);
if (empty($line)) continue;
// Convert encoding from TIS-620/Windows-874 to UTF-8
$utf8Line = @iconv('Windows-874', 'UTF-8//IGNORE', $line);
if (!$utf8Line) {
$utf8Line = @iconv('TIS-620', 'UTF-8//IGNORE', $line);
}
if (!$utf8Line) $utf8Line = $line;
$data = explode('$', $utf8Line);
if (count($data) < 70) continue; // Basic validation that it's a CGD row
$totalRecords++;
// Adjust to 1-based indexing for variables like f14, f15 to match user specs
array_unshift($data, "dummy"); // Now $data[1] is Field 1
$nationalId = trim($data[3] ?? '');
$empCode = $nationalId; // Per decision, use national_id as employee_code
if (empty($empCode)) {
$errorRecords++;
continue;
}
// --- 🔹 Fetch Names from external HR database (hosoffice_2566) ---
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
if ($hrData) {
// Get Prefix
$prefix = trim($hrData['HR_PREFIX_NAME'] ?? '');
$firstName = trim($hrData['HR_FNAME'] ?? '');
$lastName = trim($hrData['HR_LNAME'] ?? '');
// Add prefix to first name if it exists (or keep separate, based on old code it had first_name and last_name)
// Let's store prefix + first_name in first_name
if (!empty($prefix) && strpos($firstName, $prefix) === false) {
$firstName = $prefix . $firstName;
}
$position = trim(($hrData['HR_POSITION_NAME'] ?? '') . ' ' . ($hrData['HR_LEVEL_NAME'] ?? ''));
$department = trim($hrData['HR_DEPARTMENT_SUB_SUB_NAME'] ?? '');
} else {
// Fallback to text file if not found in HR DB (Column 5 and 6)
$firstName = trim($data[5] ?? '');
$lastName = trim($data[6] ?? '');
$position = '';
$department = '';
}
// -------------------------------------------------------------
// Extract Bank Details from CGD string (1-indexed due to array_unshift)
$bankName = trim($data[10] ?? '');
$bankAccount = trim($data[13] ?? '');
if (!empty($bankName) || !empty($bankAccount)) {
$profileModel->saveProfile($nationalId, $bankName, $bankAccount);
}
$baseSalaryAmount = $this->parseCGDAmount($data[14] ?? '');
$totalIncome = 0;
$incomeEntries = [];
// Base Salary (CGD) as an Income
if ($baseSalaryAmount > 0) {
$id = $this->getOrCreateIncomeMaster('CGD_INC_14', 'เงินเดือน/ค่าจ้าง (กรมบัญชีกลาง)', $incomeModel, $incomeMap);
if ($id) {
$totalIncome += $baseSalaryAmount;
$incomeEntries[] = ['id' => $id, 'amount' => $baseSalaryAmount];
}
}
// Static incomes
foreach ($cgdIncomesStatic as $index => $name) {
$amt = $this->parseCGDAmount($data[$index] ?? '');
if ($amt > 0) {
$code = "CGD_INC_" . $index;
$id = $this->getOrCreateIncomeMaster($code, $name, $incomeModel, $incomeMap);
if ($id) {
$totalIncome += $amt;
$incomeEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
// Dynamic incomes (f34->f35, etc.)
$dynamicIncomes = [
34 => 35, 36 => 37, 38 => 39, 40 => 41
];
foreach ($dynamicIncomes as $nameIdx => $valIdx) {
$name = trim($data[$nameIdx] ?? '');
$amt = $this->parseCGDAmount($data[$valIdx] ?? '');
if ($amt > 0 && !empty($name)) {
$code = "CGD_INC_" . $valIdx;
$id = $this->getOrCreateIncomeMaster($code, $name, $incomeModel, $incomeMap);
if ($id) {
$totalIncome += $amt;
$incomeEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
$totalDeduction = 0;
$deductionEntries = [];
// Static deductions
foreach ($cgdDeductionsStatic as $index => $name) {
$amt = $this->parseCGDAmount($data[$index] ?? '');
if ($amt > 0) {
$code = "CGD_DED_" . $index;
$id = $this->getOrCreateDeductionMaster($code, $name, $deductionModel, $deductionMap);
if ($id) {
$totalDeduction += $amt;
$deductionEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
// Dynamic deductions (f60->f61, etc.)
$dynamicDeductions = [
60 => 61, 62 => 63, 64 => 65, 66 => 67, 68 => 69, 70 => 71, 72 => 73, 74 => 75
];
foreach ($dynamicDeductions as $nameIdx => $valIdx) {
$name = trim($data[$nameIdx] ?? '');
$amt = $this->parseCGDAmount($data[$valIdx] ?? '');
if ($amt > 0 && !empty($name)) {
$code = "CGD_DED_" . $valIdx;
$id = $this->getOrCreateDeductionMaster($code, $name, $deductionModel, $deductionMap);
if ($id) {
$totalDeduction += $amt;
$deductionEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
$netSalary = $totalIncome - $totalDeduction;
$empData = [
'salary_month_id' => $salaryMonthId,
'employee_code' => $empCode,
'national_id' => $nationalId,
'first_name' => $firstName,
'last_name' => $lastName,
'position' => $position,
'department' => $department,
'base_salary' => 0, // Hardcode base_salary to 0 since it's now in incomes
'total_income' => $totalIncome,
'total_deduction' => $totalDeduction,
'net_salary' => $netSalary
];
$empSalId = $payrollModel->insertEmployeeSalary($empData);
foreach ($incomeEntries as $inc) {
$payrollModel->insertSalaryIncome($empSalId, $inc['id'], $inc['amount']);
}
foreach ($deductionEntries as $ded) {
$payrollModel->insertSalaryDeduction($empSalId, $ded['id'], $ded['amount']);
}
$successRecords++;
}
$payrollModel->updateSalaryMonthStatus($salaryMonthId, 'Imported');
$status = ($errorRecords > 0) ? 'partial' : 'success';
$payrollModel->logImportHistory($_SESSION['user_id'], $salaryMonthId, $fileName, $totalRecords, $successRecords, $errorRecords, $status);
$_SESSION['success'] = "นำเข้าข้อมูลกรมบัญชีกลางสำเร็จ $successRecords รายการ (ผิดพลาด $errorRecords รายการ)";
}
private function processCSVFile($handle, $salaryMonthId, $payrollModel, $incomeModel, $deductionModel, $fileName) {
$header = fgetcsv($handle, 1000, ",");
$incomeMap = $this->getIncomeMap($incomeModel);
$deductionMap = $this->getDeductionMap($deductionModel);
$colIdx = [];
foreach ($header as $index => $colName) {
$colName = trim($colName);
$colIdx[$colName] = $index;
}
$requiredCols = ['Employee_Code', 'National_ID', 'First_Name', 'Last_Name', 'Base_Salary'];
foreach ($requiredCols as $req) {
if (!isset($colIdx[$req])) {
throw new \Exception("ไฟล์ CSV ไม่ถูกต้อง ขาดคอลัมน์: $req");
}
}
$totalRecords = 0;
$successRecords = 0;
$errorRecords = 0;
while (($data = fgetcsv($handle, 1000, ",")) !== false) {
$totalRecords++;
$empCode = trim($data[$colIdx['Employee_Code']]);
if (empty($empCode)) {
$errorRecords++;
continue;
}
$baseSalary = (float)$data[$colIdx['Base_Salary']];
$totalIncome = 0;
$totalDeduction = 0;
$incomeEntries = [];
foreach ($incomeMap as $code => $id) {
if (isset($colIdx[$code])) {
$amt = (float)$data[$colIdx[$code]];
if ($amt > 0) {
$totalIncome += $amt;
$incomeEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
$deductionEntries = [];
foreach ($deductionMap as $code => $id) {
if (isset($colIdx[$code])) {
$amt = (float)$data[$colIdx[$code]];
if ($amt > 0) {
$totalDeduction += $amt;
$deductionEntries[] = ['id' => $id, 'amount' => $amt];
}
}
}
// Convert base salary to income
if ($baseSalary > 0) {
$id = $this->getOrCreateIncomeMaster('CGD_INC_14', 'เงินเดือน/ค่าจ้าง (กรมบัญชีกลาง)', $incomeModel, $incomeMap);
if ($id) {
$totalIncome += $baseSalary;
$incomeEntries[] = ['id' => $id, 'amount' => $baseSalary];
}
}
$netSalary = $totalIncome - $totalDeduction;
$empData = [
'salary_month_id' => $salaryMonthId,
'employee_code' => $empCode,
'national_id' => trim($data[$colIdx['National_ID']]),
'first_name' => trim($data[$colIdx['First_Name']]),
'last_name' => trim($data[$colIdx['Last_Name']]),
'base_salary' => 0, // Hardcode base_salary to 0
'total_income' => $totalIncome,
'total_deduction' => $totalDeduction,
'net_salary' => $netSalary
];
$empSalId = $payrollModel->insertEmployeeSalary($empData);
foreach ($incomeEntries as $inc) {
$payrollModel->insertSalaryIncome($empSalId, $inc['id'], $inc['amount']);
}
foreach ($deductionEntries as $ded) {
$payrollModel->insertSalaryDeduction($empSalId, $ded['id'], $ded['amount']);
}
$successRecords++;
}
$payrollModel->updateSalaryMonthStatus($salaryMonthId, 'Imported');
$status = ($errorRecords > 0) ? 'partial' : 'success';
$payrollModel->logImportHistory($_SESSION['user_id'], $salaryMonthId, $fileName, $totalRecords, $successRecords, $errorRecords, $status);
$_SESSION['success'] = "นำเข้าข้อมูล CSV สำเร็จ $successRecords รายการ (ผิดพลาด $errorRecords รายการ)";
}
}
@@ -0,0 +1,162 @@
<?php
namespace app\Controllers;
use app\Models\PayrollModel;
use app\Models\HrPersonModel;
use app\Models\UserLogModel;
use app\Models\SystemSettingModel;
class KioskController extends Controller {
public function index() {
// Load the Kiosk UI
$this->view('kiosk/index');
}
public function search() {
header('Content-Type: application/json');
$nationalId = $_POST['national_id'] ?? null;
if (!$nationalId || strlen($nationalId) !== 13) {
echo json_encode(['status' => 'error', 'message' => 'รหัสบัตรประชาชนไม่ถูกต้อง']);
return;
}
$payrollModel = new PayrollModel();
$hrModel = new HrPersonModel();
// Check if person exists in HR DB
$person = $hrModel->getPersonByNationalId($nationalId);
$slips = $payrollModel->getAvailableSlipsByNationalId($nationalId);
$taxes = $payrollModel->getAvailableTaxesByNationalId($nationalId);
if (empty($slips) && empty($taxes)) {
echo json_encode(['status' => 'error', 'message' => 'ไม่พบข้อมูลเงินเดือนหรือใบเสียภาษีสำหรับรหัสบัตรประชาชนนี้']);
return;
}
$personName = $person ? trim($person['hr_fname'] . ' ' . $person['hr_lname']) : 'ไม่ทราบชื่อ';
echo json_encode([
'status' => 'success',
'data' => [
'name' => $personName,
'slips' => $slips,
'taxes' => $taxes
]
]);
}
public function printSlip() {
$nationalId = $_GET['national_id'] ?? null;
$month = $_GET['month'] ?? null;
$year = $_GET['year'] ?? null;
if (!$nationalId || !$month || !$year) {
die('ข้อมูลไม่ครบถ้วน');
}
$payrollModel = new PayrollModel();
$salary = $payrollModel->getSalaryByNationalId($nationalId, $month, $year);
if (!$salary) {
die('ไม่พบข้อมูลเงินเดือนของบุคคลนี้ในงวดที่เลือก');
}
// Log the action (null user_id)
$logModel = new UserLogModel();
$logModel->logAction(null, 'KIOSK_PRINT_SLIP', "ตู้ Kiosk: พิมพ์สลิปเงินเดือน งวด $month/$year สำหรับบัตรประชาชน $nationalId");
$incomes = $payrollModel->getEmployeeIncomes($salary['id']);
$deductions = $payrollModel->getEmployeeDeductions($salary['id']);
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
if (!$hrData) {
$hrData = [];
}
require_once APP_ROOT . '/app/Views/reports/slip.php';
}
public function printSlipBulk() {
$nationalId = $_POST['national_id'] ?? null;
$periods = $_POST['periods'] ?? []; // format: 'MM-YYYY'
if (!$nationalId || empty($periods)) {
die('ข้อมูลไม่ครบถ้วน');
}
$payrollModel = new PayrollModel();
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
if (!$hrData) {
$hrData = [];
}
$bankProfile = [
'bank_account_no' => $hrData['HR_BANK_ACCOUNT'] ?? '',
'bank_name' => '' // Not provided in HR data directly without another query
];
$bulkData = [];
foreach ($periods as $period) {
list($month, $year) = explode('-', $period);
$salary = $payrollModel->getSalaryByNationalId($nationalId, $month, $year);
if ($salary) {
$bulkData[] = [
'salary' => $salary,
'hrData' => $hrData,
'bankProfile' => $bankProfile,
'incomes' => $payrollModel->getEmployeeIncomes($salary['id']),
'deductions' => $payrollModel->getEmployeeDeductions($salary['id']),
'month' => $month,
'year' => $year
];
}
}
if (empty($bulkData)) {
die('ไม่พบข้อมูลเงินเดือนสำหรับช่วงที่เลือก');
}
// Log the action
$logModel = new UserLogModel();
$logModel->logAction(null, 'KIOSK_PRINT_SLIP', "ตู้ Kiosk: พิมพ์สลิปเงินเดือนแบบกลุ่ม (" . count($periods) . " เดือน) สำหรับบัตรประชาชน $nationalId");
require_once APP_ROOT . '/app/Views/kiosk/slip_bulk.php';
}
public function printTax() {
$nationalId = $_GET['national_id'] ?? null;
$year = $_GET['year'] ?? null;
if (!$nationalId || !$year) {
die('ข้อมูลไม่ครบถ้วน');
}
$payrollModel = new PayrollModel();
$yearlySummary = $payrollModel->getYearlyTaxSummary($nationalId, $year);
if (!$yearlySummary || $yearlySummary['total_income'] == 0) {
die('ไม่พบข้อมูลรายได้ในปีที่เลือก');
}
// Log the action
$logModel = new UserLogModel();
$logModel->logAction(null, 'KIOSK_PRINT_TAX', "ตู้ Kiosk: พิมพ์ใบ 50 ทวิ ปี $year สำหรับบัตรประชาชน $nationalId");
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
if (!$hrData) {
$hrData = [];
}
$settingModel = new SystemSettingModel();
$orgTaxId = $settingModel->getSetting('org_tax_id', '');
require_once APP_ROOT . '/app/Views/reports/tax.php';
}
}
?>
@@ -0,0 +1,20 @@
<?php
namespace app\Controllers;
class ManualController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function index() {
$this->view('manual/index', [
'title' => 'คู่มือการใช้งาน | ' . APP_NAME,
'activeMenu' => 'manual'
]);
}
}
?>
@@ -0,0 +1,464 @@
<?php
namespace app\Controllers;
use app\Models\PayrollModel;
use app\Models\EmployeeProfileModel;
class PayrollController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function index() {
$payrollModel = new PayrollModel();
$periods = $payrollModel->getAllPayrollPeriods();
$this->view('payroll/index', [
'title' => 'จัดการงวดเงินเดือน | ' . APP_NAME,
'activeMenu' => 'payroll',
'periods' => $periods
]);
}
public function store() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$yearNo = isset($_POST['year_no']) ? (int)$_POST['year_no'] : 0;
$monthNo = isset($_POST['month_no']) ? (int)$_POST['month_no'] : 0;
if ($yearNo < 2000 || $monthNo < 1 || $monthNo > 12) {
$_SESSION['error'] = 'ข้อมูลปีหรือเดือนไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
if ($payrollModel->checkPeriodExists($yearNo, $monthNo)) {
$_SESSION['error'] = 'งวดเงินเดือนนี้มีอยู่ในระบบแล้ว';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
if ($payrollModel->createPayrollPeriod($yearNo, $monthNo)) {
$_SESSION['success'] = "สร้างงวดเงินเดือน $monthNo/$yearNo สำเร็จ (สถานะ: Draft)";
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการสร้างงวดเงินเดือน';
}
header('Location: ' . BASE_URL . '/payroll');
exit;
}
public function updateStatus() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
$status = $_POST['status'] ?? null;
if ($id && $status) {
$payrollModel = new PayrollModel();
$success = $payrollModel->updateSalaryMonthStatus($id, $status);
if ($success) {
$_SESSION['success'] = "อัปเดตสถานะเป็น $status สำเร็จ ข้อมูลจะแสดงที่ตู้ Kiosk แล้ว";
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการอัปเดตสถานะ';
}
} else {
$_SESSION['error'] = 'ข้อมูลไม่ครบถ้วน';
}
header('Location: ' . BASE_URL . '/payroll/details/' . $id);
exit;
}
}
public function copyPreviousMonth() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$currentMonthId = isset($_POST['salary_month_id']) ? (int)$_POST['salary_month_id'] : 0;
if (!$currentMonthId) {
$_SESSION['error'] = 'ข้อมูลไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
$currentPeriod = $payrollModel->getSalaryMonthById($currentMonthId);
if (!$currentPeriod) {
$_SESSION['error'] = 'ไม่พบงวดเงินเดือนที่ระบุ';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
if ($currentPeriod['status'] !== 'Draft') {
$_SESSION['error'] = 'ไม่สามารถเพิ่มข้อมูลได้เนื่องจากงวดเงินเดือนถูกล็อคแล้ว';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $currentMonthId);
exit;
}
// Find previous month
$prevMonthId = $payrollModel->getPreviousSalaryMonth($currentPeriod['year_no'], $currentPeriod['month_no']);
if (!$prevMonthId) {
$_SESSION['error'] = 'ไม่พบข้อมูลงวดเงินเดือนของเดือนก่อนหน้าในระบบ';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $currentMonthId);
exit;
}
$result = $payrollModel->copyFromPreviousMonth($currentMonthId, $prevMonthId);
if ($result !== false) {
$copiedCount = $result['copied'];
$mergedCount = $result['merged'];
if ($copiedCount > 0 || $mergedCount > 0) {
$_SESSION['success'] = "คัดลอกข้อมูลสำเร็จ: เพิ่มพนักงานใหม่ $copiedCount คน, อัปเดตรายการรับจ่ายของโรงพยาบาล $mergedCount คน";
} else {
$_SESSION['success'] = 'ไม่มีข้อมูลที่ต้องคัดลอกเพิ่มเติม (ข้อมูลถูกคัดลอกมาหมดแล้ว)';
}
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการคัดลอกข้อมูล';
}
header('Location: ' . BASE_URL . '/payroll/details?id=' . $currentMonthId);
exit;
}
public function clearData() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$salaryMonthId = isset($_POST['salary_month_id']) ? (int)$_POST['salary_month_id'] : 0;
if ($salaryMonthId <= 0) {
$_SESSION['error'] = 'ไม่พบข้อมูลงวดเงินเดือนที่ต้องการล้าง';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
$period = $payrollModel->getSalaryMonthById($salaryMonthId);
if (!$period) {
$_SESSION['error'] = 'ไม่พบข้อมูลงวดเงินเดือนในระบบ';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
if ($period['status'] === 'Locked' || $period['status'] === 'Published') {
$_SESSION['error'] = 'ไม่สามารถล้างข้อมูลได้เนื่องจากงวดเงินเดือนถูกล็อกหรือประกาศใช้แล้ว';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
if ($payrollModel->clearDataForPeriod($salaryMonthId)) {
$_SESSION['success'] = 'ล้างข้อมูลการนำเข้าสำหรับงวดเงินเดือนนี้เรียบร้อยแล้ว';
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการล้างข้อมูล';
}
header('Location: ' . BASE_URL . '/payroll');
exit;
}
public function details() {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
$_SESSION['error'] = 'ไม่พบข้อมูลงวดเงินเดือน';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
$period = $payrollModel->getSalaryMonthById($id);
if (!$period) {
$_SESSION['error'] = 'ไม่พบข้อมูลงวดเงินเดือนในระบบ';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$employees = $payrollModel->getEmployeesBySalaryMonthId($id);
$stats = $payrollModel->getPeriodStats($id);
// Map dynamic HR data
$cids = array_column($employees, 'national_id');
$hrModel = new \app\Models\HrPersonModel();
$hrPersons = $hrModel->getPersonsByNationalIds($cids);
foreach ($employees as &$emp) {
$cid = $emp['national_id'];
if (isset($hrPersons[$cid])) {
$hr = $hrPersons[$cid];
$prefix = trim($hr['HR_PREFIX_NAME'] ?? '');
$firstName = trim($hr['HR_FNAME'] ?? '');
if (!empty($prefix) && strpos($firstName, $prefix) === false) {
$firstName = $prefix . $firstName;
}
$emp['first_name'] = $firstName;
$emp['last_name'] = trim($hr['HR_LNAME'] ?? '');
$emp['position'] = trim(($hr['HR_POSITION_NAME'] ?? '') . ' ' . ($hr['HR_LEVEL_NAME'] ?? ''));
$emp['department'] = trim($hr['HR_DEPARTMENT_SUB_SUB_NAME'] ?? '');
}
}
unset($emp);
$this->view('payroll/details', [
'title' => 'รายละเอียดงวดเงินเดือน | ' . APP_NAME,
'activeMenu' => 'payroll',
'period' => $period,
'employees' => $employees,
'stats' => $stats
]);
}
public function addEmployee() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$salaryMonthId = isset($_POST['salary_month_id']) ? (int)$_POST['salary_month_id'] : 0;
$nationalId = trim($_POST['national_id'] ?? '');
if ($salaryMonthId <= 0 || empty($nationalId)) {
$_SESSION['error'] = 'ข้อมูลไม่ครบถ้วน กรุณากรอกเลขบัตรประชาชน';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
// 1. Fetch from HR DB
$hrModel = new \app\Models\HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
if (!$hrData) {
$_SESSION['error'] = 'ไม่พบข้อมูลบุคคลนี้ในฐานข้อมูล HR (hosoffice_2566)';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
// 2. Prepare Data
$prefix = trim($hrData['HR_PREFIX_NAME'] ?? '');
$firstName = trim($hrData['HR_FNAME'] ?? '');
$lastName = trim($hrData['HR_LNAME'] ?? '');
if (!empty($prefix) && strpos($firstName, $prefix) === false) {
$firstName = $prefix . $firstName;
}
$position = trim(($hrData['HR_POSITION_NAME'] ?? '') . ' ' . ($hrData['HR_LEVEL_NAME'] ?? ''));
$department = trim($hrData['HR_DEPARTMENT_SUB_SUB_NAME'] ?? '');
$data = [
'salary_month_id' => $salaryMonthId,
'employee_code' => $nationalId,
'national_id' => $nationalId,
'first_name' => $firstName,
'last_name' => $lastName,
'position' => $position,
'department' => $department,
'base_salary' => 0,
'total_income' => 0,
'total_deduction' => 0,
'net_salary' => 0
];
// 3. Save
$payrollModel = new PayrollModel();
$payrollModel->insertEmployeeSalary($data);
$_SESSION['success'] = 'เพิ่มรายชื่อพนักงานสำเร็จ: ' . $firstName . ' ' . $lastName;
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
public function deleteEmployee() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$salaryMonthId = isset($_POST['salary_month_id']) ? (int)$_POST['salary_month_id'] : 0;
if ($id <= 0 || $salaryMonthId <= 0) {
$_SESSION['error'] = 'ข้อมูลไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
$payrollModel = new PayrollModel();
if ($payrollModel->deleteEmployeeSalary($id)) {
$_SESSION['success'] = 'ลบข้อมูลพนักงานออกจากงวดนี้สำเร็จ';
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการลบข้อมูล';
}
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
public function editEmployee() {
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
$_SESSION['error'] = 'ไม่พบข้อมูลพนักงาน';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
$employee = $payrollModel->getEmployeeSalaryById($id);
if (!$employee) {
$_SESSION['error'] = 'ไม่พบข้อมูลพนักงานในระบบ';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$period = $payrollModel->getSalaryMonthById($employee['salary_month_id']);
if ($period['status'] === 'Locked' || $period['status'] === 'Published') {
$_SESSION['error'] = 'งวดเงินเดือนถูกล็อกแล้ว ไม่สามารถแก้ไขข้อมูลได้';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $employee['salary_month_id']);
exit;
}
$incomes = $payrollModel->getEmployeeIncomes($id);
$deductions = $payrollModel->getEmployeeDeductions($id);
// Fetch HR data dynamically
$hrModel = new \app\Models\HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($employee['national_id']);
if ($hrData) {
$prefix = trim($hrData['HR_PREFIX_NAME'] ?? '');
$firstName = trim($hrData['HR_FNAME'] ?? '');
if (!empty($prefix) && strpos($firstName, $prefix) === false) {
$firstName = $prefix . $firstName;
}
$employee['first_name'] = $firstName;
$employee['last_name'] = trim($hrData['HR_LNAME'] ?? '');
$employee['position'] = trim(($hrData['HR_POSITION_NAME'] ?? '') . ' ' . ($hrData['HR_LEVEL_NAME'] ?? ''));
$employee['department'] = trim($hrData['HR_DEPARTMENT_SUB_SUB_NAME'] ?? '');
}
// Fetch master data for dropdowns
$incomeModel = new \app\Models\IncomeMasterModel();
$masterIncomes = $incomeModel->getActiveIncomes();
$deductionModel = new \app\Models\DeductionMasterModel();
$masterDeductions = $deductionModel->getActiveDeductions();
// Fetch bank profile
$profileModel = new EmployeeProfileModel();
$profile = $profileModel->getProfile($employee['national_id']);
if ($profile) {
$employee['bank_name'] = $profile['bank_name'];
$employee['bank_account'] = $profile['bank_account'];
} else {
$employee['bank_name'] = 'ธนาคารกรุงไทย จำกัด(มหาชน)';
$employee['bank_account'] = '';
}
$this->view('payroll/employee_edit', [
'title' => 'แก้ไขข้อมูลเงินเดือน | ' . APP_NAME,
'activeMenu' => 'payroll',
'employee' => $employee,
'period' => $period,
'incomes' => $incomes,
'deductions' => $deductions,
'masterIncomes' => $masterIncomes,
'masterDeductions' => $masterDeductions
]);
}
public function updateEmployee() {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$id = isset($_POST['employee_salary_id']) ? (int)$_POST['employee_salary_id'] : 0;
$salaryMonthId = isset($_POST['salary_month_id']) ? (int)$_POST['salary_month_id'] : 0;
if ($id <= 0 || $salaryMonthId <= 0) {
$_SESSION['error'] = 'ข้อมูลไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/payroll');
exit;
}
$payrollModel = new PayrollModel();
$period = $payrollModel->getSalaryMonthById($salaryMonthId);
if ($period['status'] === 'Locked' || $period['status'] === 'Published') {
$_SESSION['error'] = 'งวดเงินเดือนถูกล็อกแล้ว ไม่สามารถแก้ไขข้อมูลได้';
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
$totalIncome = 0.0;
$incomesData = [];
if (isset($_POST['income_id']) && is_array($_POST['income_id'])) {
for ($i = 0; $i < count($_POST['income_id']); $i++) {
$incId = (int)$_POST['income_id'][$i];
$amt = (float)str_replace(',', '', $_POST['income_amount'][$i]);
if ($incId > 0 && $amt > 0) {
$incomesData[] = ['id' => $incId, 'amount' => $amt];
$totalIncome += $amt;
}
}
}
$totalDeduction = 0.0;
$deductionsData = [];
if (isset($_POST['deduction_id']) && is_array($_POST['deduction_id'])) {
for ($i = 0; $i < count($_POST['deduction_id']); $i++) {
$dedId = (int)$_POST['deduction_id'][$i];
$amt = (float)str_replace(',', '', $_POST['deduction_amount'][$i]);
if ($dedId > 0 && $amt > 0) {
$deductionsData[] = ['id' => $dedId, 'amount' => $amt];
$totalDeduction += $amt;
}
}
}
$netSalary = $totalIncome - $totalDeduction;
// Save Profile (Bank info)
$bankName = trim($_POST['bank_name'] ?? '');
$bankAccount = trim($_POST['bank_account'] ?? '');
$empRecord = $payrollModel->getEmployeeSalaryById($id);
if ($empRecord) {
$profileModel = new EmployeeProfileModel();
$profileModel->saveProfile($empRecord['national_id'], $bankName, $bankAccount);
}
if ($payrollModel->updateEmployeeSalaryDetails($id, 0, $incomesData, $deductionsData, $totalIncome, $totalDeduction, $netSalary)) {
$_SESSION['success'] = 'อัปเดตข้อมูลเงินเดือนเรียบร้อยแล้ว';
} else {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการบันทึกข้อมูล';
}
header('Location: ' . BASE_URL . '/payroll/details?id=' . $salaryMonthId);
exit;
}
}
?>
@@ -0,0 +1,71 @@
<?php
namespace app\Controllers;
use app\Models\UserModel;
use app\Models\UserLogModel;
class ProfileController extends Controller {
public function index() {
$userModel = new UserModel();
$user = $userModel->getUserById($_SESSION['user_id']);
$roleName = $user['role_id'] == 1 ? 'ผู้ดูแลระบบ (Admin)' : 'ผู้ใช้งานทั่วไป (User)';
$this->view('profile/index', [
'title' => 'ข้อมูลส่วนตัว | ' . APP_NAME,
'user' => $user,
'roleName' => $roleName
]);
}
public function updatePassword() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
if (empty($currentPassword) || empty($newPassword) || empty($confirmPassword)) {
$_SESSION['error'] = 'กรุณากรอกข้อมูลให้ครบถ้วน';
header('Location: ' . BASE_URL . '/profile');
exit;
}
if ($newPassword !== $confirmPassword) {
$_SESSION['error'] = 'รหัสผ่านใหม่ไม่ตรงกัน';
header('Location: ' . BASE_URL . '/profile');
exit;
}
if (strlen($newPassword) < 6) {
$_SESSION['error'] = 'รหัสผ่านใหม่ต้องมีความยาวอย่างน้อย 6 ตัวอักษร';
header('Location: ' . BASE_URL . '/profile');
exit;
}
$userModel = new UserModel();
$user = $userModel->getUserById($_SESSION['user_id']);
if (!$user || !password_verify($currentPassword, $user['password_hash'])) {
$_SESSION['error'] = 'รหัสผ่านปัจจุบันไม่ถูกต้อง';
header('Location: ' . BASE_URL . '/profile');
exit;
}
try {
$userModel->updatePassword($_SESSION['user_id'], $newPassword);
$logModel = new UserLogModel();
$logModel->logAction($_SESSION['user_id'], 'UPDATE_USER', 'เปลี่ยนรหัสผ่านของตนเองผ่านหน้าข้อมูลส่วนตัว');
$_SESSION['success'] = 'เปลี่ยนรหัสผ่านเรียบร้อยแล้ว';
} catch (\Exception $e) {
$_SESSION['error'] = 'เกิดข้อผิดพลาด ไม่สามารถเปลี่ยนรหัสผ่านได้';
}
header('Location: ' . BASE_URL . '/profile');
exit;
}
}
}
?>
@@ -0,0 +1,427 @@
<?php
namespace app\Controllers;
use app\Models\PayrollModel;
use app\Models\HrPersonModel;
use app\Models\IncomeMasterModel;
use app\Models\DeductionMasterModel;
use app\Models\OfficerTypeModel;
class ReportController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
public function index() {
$payrollModel = new PayrollModel();
// Get filter inputs
$month = $_GET['month'] ?? date('m');
$year = $_GET['year'] ?? date('Y');
// Fetch payroll data for the selected month/year
$payrollData = $payrollModel->getSalariesByPeriod($month, $year);
// Fetch matching HR data
$hrModel = new HrPersonModel();
$nationalIds = array_column($payrollData, 'national_id');
$hrData = $hrModel->getPersonsByNationalIds($nationalIds);
// Combine data
foreach ($payrollData as &$row) {
$nid = $row['national_id'];
if (isset($hrData[$nid])) {
$row['hr_fname'] = $hrData[$nid]['HR_FNAME'];
$row['hr_lname'] = $hrData[$nid]['HR_LNAME'];
$row['hr_position'] = $hrData[$nid]['HR_POSITION_NAME'];
$row['hr_department'] = $hrData[$nid]['HR_DEPARTMENT_SUB_SUB_NAME'];
}
}
$this->view('reports/index', [
'title' => 'ระบบสืบค้นและพิมพ์สลิปเงินเดือน | ' . APP_NAME,
'activeMenu' => 'reports',
'month' => $month,
'year' => $year,
'payrollData' => $payrollData
]);
}
public function printSlip() {
$nationalId = $_GET['national_id'] ?? null;
$month = $_GET['month'] ?? null;
$year = $_GET['year'] ?? null;
if (!$nationalId || !$month || !$year) {
die('ข้อมูลไม่ครบถ้วน');
}
$payrollModel = new PayrollModel();
$salary = $payrollModel->getSalaryByNationalId($nationalId, $month, $year);
if (!$salary) {
die('ไม่พบข้อมูลเงินเดือนของบุคคลนี้ในงวดที่เลือก');
}
$incomes = $payrollModel->getEmployeeIncomes($salary['id']);
$deductions = $payrollModel->getEmployeeDeductions($salary['id']);
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
$profileModel = new \app\Models\EmployeeProfileModel();
$bankProfile = $profileModel->getProfile($nationalId);
$settingModel = new \app\Models\SystemSettingModel();
$signatureName = $settingModel->getSetting('signature_name', '( นางณัฐฐิณี เรืองทอง )');
$signaturePosition = $settingModel->getSetting('signature_position', 'นักวิชาการเงินและบัญชี');
$signatureScale = $settingModel->getSetting('signature_scale', '100');
$slipFontSize = $settingModel->getSetting('slip_font_size', '11pt');
$signaturePosX = $settingModel->getSetting('signature_pos_x', '0');
$signaturePosY = $settingModel->getSetting('signature_pos_y', '0');
$this->view('reports/slip', [
'salary' => $salary,
'incomes' => $incomes,
'deductions' => $deductions,
'hrData' => $hrData,
'bankProfile' => $bankProfile,
'signatureName' => $signatureName,
'signaturePosition' => $signaturePosition,
'signatureScale' => $signatureScale,
'slipFontSize' => $slipFontSize,
'signaturePosX' => $signaturePosX,
'signaturePosY' => $signaturePosY,
'month' => $month,
'year' => $year
]);
}
public function printTax() {
$nationalId = $_GET['national_id'] ?? null;
$year = $_GET['year'] ?? null;
if (!$nationalId || !$year) {
die('ข้อมูลไม่ครบถ้วน');
}
$payrollModel = new PayrollModel();
$yearlySummary = $payrollModel->getYearlyTaxSummary($nationalId, $year);
if (!$yearlySummary || $yearlySummary['total_income'] == 0) {
die('ไม่พบข้อมูลรายได้ในปีที่เลือก');
}
$hrModel = new HrPersonModel();
$hrData = $hrModel->getPersonByNationalId($nationalId);
$settingModel = new \app\Models\SystemSettingModel();
$orgTaxId = $settingModel->getSetting('org_tax_id', '');
$taxSignatureName = $settingModel->getSetting('tax_signature_name', '');
$taxSignaturePosition = $settingModel->getSetting('tax_signature_position', '');
$taxSignatureScale = $settingModel->getSetting('tax_signature_scale', '100');
$taxSignaturePosX = $settingModel->getSetting('tax_signature_pos_x', '0');
$taxSignaturePosY = $settingModel->getSetting('tax_signature_pos_y', '0');
$taxFontSize = $settingModel->getSetting('tax_font_size', '11pt');
$orgAddress = $settingModel->getSetting('org_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$taxPayeeAddress = $settingModel->getSetting('tax_payee_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$this->view('reports/tax', [
'yearlySummary' => $yearlySummary,
'hrData' => $hrData,
'year' => $year,
'nationalId' => $nationalId,
'orgTaxId' => $orgTaxId,
'taxSignatureName' => $taxSignatureName,
'taxSignaturePosition' => $taxSignaturePosition,
'taxSignatureScale' => $taxSignatureScale,
'taxSignaturePosX' => $taxSignaturePosX,
'taxSignaturePosY' => $taxSignaturePosY,
'taxFontSize' => $taxFontSize,
'orgAddress' => $orgAddress,
'taxPayeeAddress' => $taxPayeeAddress
]);
}
public function printTaxBulk() {
$nationalIds = $_POST['national_ids'] ?? [];
$year = $_POST['year'] ?? null;
$groupByLetter = isset($_POST['group_by_letter']) && $_POST['group_by_letter'] == '1';
if (empty($nationalIds) || !$year) {
die('ข้อมูลไม่ครบถ้วน กรุณาเลือกพนักงานอย่างน้อย 1 รายการ');
}
$payrollModel = new PayrollModel();
$hrModel = new HrPersonModel();
$bulkData = [];
foreach ($nationalIds as $nationalId) {
$yearlySummary = $payrollModel->getYearlyTaxSummary($nationalId, $year);
if ($yearlySummary && $yearlySummary['total_income'] > 0) {
$hrData = $hrModel->getPersonByNationalId($nationalId);
$bulkData[] = [
'nationalId' => $nationalId,
'yearlySummary' => $yearlySummary,
'hrData' => $hrData
];
}
}
if (empty($bulkData)) {
die('ไม่พบข้อมูลรายได้ในปีที่เลือกของพนักงานที่เลือกทั้งหมด');
}
// Sort data by First Name if grouping by letter is enabled
if ($groupByLetter) {
usort($bulkData, function($a, $b) {
$nameA = $a['hrData']['HR_FNAME'] ?? '';
$nameB = $b['hrData']['HR_FNAME'] ?? '';
return strcmp($nameA, $nameB);
});
}
$settingModel = new \app\Models\SystemSettingModel();
$orgTaxId = $settingModel->getSetting('org_tax_id', '');
$taxSignatureName = $settingModel->getSetting('tax_signature_name', '');
$taxSignaturePosition = $settingModel->getSetting('tax_signature_position', '');
$taxSignatureScale = $settingModel->getSetting('tax_signature_scale', '100');
$taxSignaturePosX = $settingModel->getSetting('tax_signature_pos_x', '0');
$taxSignaturePosY = $settingModel->getSetting('tax_signature_pos_y', '0');
$taxFontSize = $settingModel->getSetting('tax_font_size', '11pt');
$orgAddress = $settingModel->getSetting('org_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$taxPayeeAddress = $settingModel->getSetting('tax_payee_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$this->view('reports/tax_bulk', [
'bulkData' => $bulkData,
'year' => $year,
'groupByLetter' => $groupByLetter,
'orgTaxId' => $orgTaxId,
'taxSignatureName' => $taxSignatureName,
'taxSignaturePosition' => $taxSignaturePosition,
'taxSignatureScale' => $taxSignatureScale,
'taxSignaturePosX' => $taxSignaturePosX,
'taxSignaturePosY' => $taxSignaturePosY,
'taxFontSize' => $taxFontSize,
'orgAddress' => $orgAddress,
'taxPayeeAddress' => $taxPayeeAddress
]);
}
public function salaryReport() {
$payrollModel = new PayrollModel();
$hrModel = new HrPersonModel();
$officerTypeModel = new OfficerTypeModel();
$month = $_GET['month'] ?? date('m');
$year = $_GET['year'] ?? date('Y');
$typeId = $_GET['type_id'] ?? '';
$officerTypes = $officerTypeModel->getActive();
$selectedTypeName = '';
if ($typeId) {
foreach ($officerTypes as $type) {
if ($type['id'] == $typeId) {
$selectedTypeName = $type['name'];
break;
}
}
}
$payrollData = $payrollModel->getSalariesByPeriod($month, $year);
$nationalIds = array_column($payrollData, 'national_id');
$hrData = $hrModel->getPersonsByNationalIds($nationalIds);
$reportData = [];
foreach ($payrollData as $row) {
$nid = $row['national_id'];
if (isset($hrData[$nid])) {
$personType = $hrData[$nid]['HR_PERSON_TYPE_NAME'] ?? '';
// Filter by type if selected
if ($typeId && $personType !== $selectedTypeName) {
continue;
}
$row['hr_prefix'] = $hrData[$nid]['HR_PREFIX_NAME'] ?? '';
$row['hr_fname'] = $hrData[$nid]['HR_FNAME'] ?? '';
$row['hr_lname'] = $hrData[$nid]['HR_LNAME'] ?? '';
$row['hr_position'] = $hrData[$nid]['HR_POSITION_NAME'] ?? '';
$row['hr_department'] = $hrData[$nid]['HR_DEPARTMENT_SUB_SUB_NAME'] ?? '';
$row['person_type'] = $personType;
// Fetch incomes and deductions to display sums or details if needed
// But for performance, we can just use the cached totals if available.
// Wait, if we need specific columns for Excel, we should fetch details.
// In this case, we fetch them here or in export mode.
$reportData[] = $row;
}
}
// Sort by first name (ignoring prefix)
usort($reportData, function($a, $b) {
return strcmp($a['hr_fname'], $b['hr_fname']);
});
$this->view('reports/salary_report', [
'title' => 'รายงานเงินเดือน | ' . APP_NAME,
'activeMenu' => 'report_salary',
'month' => $month,
'year' => $year,
'typeId' => $typeId,
'officerTypes' => $officerTypes,
'reportData' => $reportData
]);
}
public function exportExcel() {
require_once APP_ROOT . '/app/Libraries/SimpleXLSXGen.php';
$payrollModel = new PayrollModel();
$hrModel = new HrPersonModel();
$officerTypeModel = new OfficerTypeModel();
$incomeModel = new IncomeMasterModel();
$deductionModel = new DeductionMasterModel();
$month = $_GET['month'] ?? date('m');
$year = $_GET['year'] ?? date('Y');
$typeId = $_GET['type_id'] ?? '';
$officerTypes = $officerTypeModel->getActive();
$selectedTypeName = 'ทั้งหมด';
if ($typeId) {
foreach ($officerTypes as $type) {
if ($type['id'] == $typeId) {
$selectedTypeName = $type['name'];
break;
}
}
}
$payrollData = $payrollModel->getSalariesByPeriod($month, $year);
$nationalIds = array_column($payrollData, 'national_id');
$hrData = $hrModel->getPersonsByNationalIds($nationalIds);
// Fetch all income/deduction codes to build dynamic columns
$allIncomes = $incomeModel->getAllIncomes();
$allDeductions = $deductionModel->getAllDeductions();
$incomeCols = [];
foreach ($allIncomes as $inc) {
$incomeCols[$inc['code']] = $inc['name'];
}
$deductionCols = [];
foreach ($allDeductions as $ded) {
$deductionCols[$ded['code']] = $ded['name'];
}
$reportData = [];
foreach ($payrollData as $row) {
$nid = $row['national_id'];
if (isset($hrData[$nid])) {
$personType = $hrData[$nid]['HR_PERSON_TYPE_NAME'] ?? '';
if ($typeId && $personType !== $selectedTypeName) {
continue;
}
$row['hr_prefix'] = $hrData[$nid]['HR_PREFIX_NAME'] ?? '';
$row['hr_fname'] = $hrData[$nid]['HR_FNAME'] ?? '';
$row['hr_lname'] = $hrData[$nid]['HR_LNAME'] ?? '';
// Fetch details
$incomes = $payrollModel->getEmployeeIncomes($row['id']);
$deductions = $payrollModel->getEmployeeDeductions($row['id']);
$row['incomes_detail'] = [];
foreach ($incomes as $inc) {
$row['incomes_detail'][$inc['income_code']] = $inc['amount'];
}
$row['deductions_detail'] = [];
foreach ($deductions as $ded) {
$row['deductions_detail'][$ded['deduction_code']] = $ded['amount'];
}
$reportData[] = $row;
}
}
usort($reportData, function($a, $b) {
return strcmp($a['hr_fname'], $b['hr_fname']);
});
$monthNames = [
'01' => 'มกราคม', '02' => 'กุมภาพันธ์', '03' => 'มีนาคม', '04' => 'เมษายน',
'05' => 'พฤษภาคม', '06' => 'มิถุนายน', '07' => 'กรกฎาคม', '08' => 'สิงหาคม',
'09' => 'กันยายน', '10' => 'ตุลาคม', '11' => 'พฤศจิกายน', '12' => 'ธันวาคม'
];
$monthName = $monthNames[$month] ?? $month;
$thaiYear = $year + 543;
$excelData = [];
// Header
$excelData[] = ["รายงานเงินเดือนเจ้าหน้าที่โรงพยาบาล"];
$excelData[] = ["ประจำเดือน: $monthName $thaiYear", "", "ประเภท: $selectedTypeName"];
$excelData[] = [];
// Columns
$headerRow = ["ลำดับ", "ชื่อ-สกุล", "เงินเดือนหลัก"];
foreach ($incomeCols as $code => $name) {
$headerRow[] = $name;
}
$headerRow[] = "รวมรายรับ";
foreach ($deductionCols as $code => $name) {
$headerRow[] = $name;
}
$headerRow[] = "รวมรายจ่าย";
$headerRow[] = "รับสุทธิ";
$excelData[] = $headerRow;
// Data Rows
$idx = 1;
foreach ($reportData as $row) {
$fullName = trim($row['hr_prefix']) . trim($row['hr_fname']) . ' ' . trim($row['hr_lname']);
$dataRow = [
$idx++,
$fullName,
$row['base_salary']
];
// Incomes
foreach ($incomeCols as $code => $name) {
$dataRow[] = $row['incomes_detail'][$code] ?? 0;
}
$dataRow[] = $row['total_income'];
// Deductions
foreach ($deductionCols as $code => $name) {
$dataRow[] = $row['deductions_detail'][$code] ?? 0;
}
$dataRow[] = $row['total_deduction'];
$dataRow[] = $row['net_salary'];
$excelData[] = $dataRow;
}
$xlsx = \Shuchkin\SimpleXLSXGen::fromArray($excelData);
$xlsx->freezePanes('A5');
$filename = "รายงานเงินเดือนเจ้าหน้าที่_{$month}_{$year}.xlsx";
$xlsx->downloadAs($filename);
exit;
}
}
@@ -0,0 +1,429 @@
<?php
namespace app\Controllers;
use app\Models\IncomeMasterModel;
use app\Models\DeductionMasterModel;
use app\Models\SystemSettingModel;
use app\Models\HrPersonModel;
use app\Models\OfficerTypeModel;
class SettingController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
}
// --- Income Methods ---
public function income() {
$incomeModel = new IncomeMasterModel();
$incomes = $incomeModel->getAllIncomes();
$this->view('settings/income', [
'title' => 'จัดการรายการรายรับ | ' . APP_NAME,
'activeMenu' => 'setting_income',
'incomes' => $incomes
]);
}
public function storeIncome() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'code' => $_POST['code'] ?? '',
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'is_taxable' => isset($_POST['is_taxable']) ? 1 : 0,
'is_active' => isset($_POST['is_active']) ? 1 : 0,
'fund_source' => $_POST['fund_source'] ?? 'HOSPITAL',
'display_order' => $_POST['display_order'] ?? 0
];
$incomeModel = new IncomeMasterModel();
try {
$incomeModel->createIncome($data);
$_SESSION['success'] = 'เพิ่มรายการรายรับสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถเพิ่มรายการได้ อาจรหัสซ้ำ (' . $e->getMessage() . ')';
}
header('Location: ' . BASE_URL . '/settings/income');
exit;
}
}
public function updateIncome() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
if (!$id) {
$_SESSION['error'] = 'ไม่พบรหัสรายการ';
header('Location: ' . BASE_URL . '/settings/income');
exit;
}
$data = [
'code' => $_POST['code'] ?? '',
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'is_taxable' => isset($_POST['is_taxable']) ? 1 : 0,
'is_active' => isset($_POST['is_active']) ? 1 : 0,
'fund_source' => $_POST['fund_source'] ?? 'HOSPITAL',
'display_order' => $_POST['display_order'] ?? 0
];
$incomeModel = new IncomeMasterModel();
try {
$incomeModel->updateIncome($id, $data);
$_SESSION['success'] = 'แก้ไขรายการรายรับสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถแก้ไขรายการได้ (' . $e->getMessage() . ')';
}
header('Location: ' . BASE_URL . '/settings/income');
exit;
}
}
// --- Deduction Methods ---
public function deduction() {
$deductionModel = new DeductionMasterModel();
$deductions = $deductionModel->getAllDeductions();
$this->view('settings/deduction', [
'title' => 'จัดการรายการรายจ่าย | ' . APP_NAME,
'activeMenu' => 'setting_deduction',
'deductions' => $deductions
]);
}
public function storeDeduction() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'code' => $_POST['code'] ?? '',
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'is_tax_deductible' => isset($_POST['is_tax_deductible']) ? 1 : 0,
'is_active' => isset($_POST['is_active']) ? 1 : 0,
'fund_source' => $_POST['fund_source'] ?? 'HOSPITAL',
'display_order' => $_POST['display_order'] ?? 0
];
$deductionModel = new DeductionMasterModel();
try {
$deductionModel->createDeduction($data);
$_SESSION['success'] = 'เพิ่มรายการรายจ่ายสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถเพิ่มรายการได้ อาจรหัสซ้ำ (' . $e->getMessage() . ')';
}
header('Location: ' . BASE_URL . '/settings/deduction');
exit;
}
}
public function updateDeduction() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
if (!$id) {
$_SESSION['error'] = 'ไม่พบรหัสรายการ';
header('Location: ' . BASE_URL . '/settings/deduction');
exit;
}
$data = [
'code' => $_POST['code'] ?? '',
'name' => $_POST['name'] ?? '',
'description' => $_POST['description'] ?? '',
'is_tax_deductible' => isset($_POST['is_tax_deductible']) ? 1 : 0,
'is_active' => isset($_POST['is_active']) ? 1 : 0,
'fund_source' => $_POST['fund_source'] ?? 'HOSPITAL',
'display_order' => $_POST['display_order'] ?? 0
];
$deductionModel = new DeductionMasterModel();
try {
$deductionModel->updateDeduction($id, $data);
$_SESSION['success'] = 'แก้ไขรายการรายจ่ายสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถแก้ไขรายการได้ (' . $e->getMessage() . ')';
}
header('Location: ' . BASE_URL . '/settings/deduction');
exit;
}
}
// --- System Methods ---
public function system() {
$settingModel = new SystemSettingModel();
$orgTaxId = $settingModel->getSetting('org_tax_id', '');
$appVersion = $settingModel->getSetting('app_version', '1.0.0');
$signatureName = $settingModel->getSetting('signature_name', '');
$signaturePosition = $settingModel->getSetting('signature_position', '');
$signatureScale = $settingModel->getSetting('signature_scale', '100');
$slipFontSize = $settingModel->getSetting('slip_font_size', '11pt');
$signaturePosX = $settingModel->getSetting('signature_pos_x', '0');
$signaturePosY = $settingModel->getSetting('signature_pos_y', '0');
$chartHistoryYears = $settingModel->getSetting('chart_history_years', '3');
$this->view('settings/system', [
'title' => 'ตั้งค่าระบบทั่วไป | ' . APP_NAME,
'activeMenu' => 'setting_system',
'orgTaxId' => $orgTaxId,
'appVersion' => $appVersion,
'signatureName' => $signatureName,
'signaturePosition' => $signaturePosition,
'signatureScale' => $signatureScale,
'slipFontSize' => $slipFontSize,
'signaturePosX' => $signaturePosX,
'signaturePosY' => $signaturePosY,
'chartHistoryYears' => $chartHistoryYears,
'taxSignatureName' => $settingModel->getSetting('tax_signature_name', ''),
'taxSignaturePosition' => $settingModel->getSetting('tax_signature_position', ''),
'taxSignatureScale' => $settingModel->getSetting('tax_signature_scale', '100'),
'taxSignaturePosX' => $settingModel->getSetting('tax_signature_pos_x', '0'),
'taxSignaturePosY' => $settingModel->getSetting('tax_signature_pos_y', '0'),
'taxFontSize' => $settingModel->getSetting('tax_font_size', '11pt'),
'orgAddress' => $settingModel->getSetting('org_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140'),
'taxPayeeAddress' => $settingModel->getSetting('tax_payee_address', 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140')
]);
}
public function saveSystemSettings() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$settingModel = new SystemSettingModel();
$settingModel->setSetting('org_tax_id', $_POST['org_tax_id'] ?? '');
$settingModel->setSetting('app_version', $_POST['app_version'] ?? '1.0.0');
$settingModel->setSetting('signature_name', $_POST['signature_name'] ?? '');
$settingModel->setSetting('signature_position', $_POST['signature_position'] ?? '');
$settingModel->setSetting('signature_scale', $_POST['signature_scale'] ?? '100');
$settingModel->setSetting('slip_font_size', $_POST['slip_font_size'] ?? '11pt');
$settingModel->setSetting('signature_pos_x', $_POST['signature_pos_x'] ?? '0');
$settingModel->setSetting('signature_pos_y', $_POST['signature_pos_y'] ?? '0');
$settingModel->setSetting('chart_history_years', $_POST['chart_history_years'] ?? '3');
$settingModel->setSetting('tax_signature_name', $_POST['tax_signature_name'] ?? '');
$settingModel->setSetting('tax_signature_position', $_POST['tax_signature_position'] ?? '');
$settingModel->setSetting('tax_signature_scale', $_POST['tax_signature_scale'] ?? '100');
$settingModel->setSetting('tax_signature_pos_x', $_POST['tax_signature_pos_x'] ?? '0');
$settingModel->setSetting('tax_signature_pos_y', $_POST['tax_signature_pos_y'] ?? '0');
$settingModel->setSetting('tax_font_size', $_POST['tax_font_size'] ?? '11pt');
$settingModel->setSetting('org_address', $_POST['org_address'] ?? 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$settingModel->setSetting('tax_payee_address', $_POST['tax_payee_address'] ?? 'ต.อ่างทอง อ.เกาะสมุย จ.สุราษฎร์ธานี 84140');
$_SESSION['success'] = 'บันทึกการตั้งค่าระบบเรียบร้อยแล้ว';
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
}
public function searchOfficer() {
error_reporting(0); // Supress all errors to prevent breaking JSON
header('Content-Type: application/json; charset=utf-8');
$keyword = $_GET['q'] ?? '';
$response = [
'status' => 'error',
'message' => 'ไม่พบข้อมูล',
'data' => []
];
if (strlen($keyword) >= 2) {
try {
$hrModel = new HrPersonModel();
$results = $hrModel->searchPersons($keyword);
if (!empty($results)) {
$formattedData = [];
foreach ($results as $person) {
// User's formatting logic
$fullName = ($person['HR_PREFIX_NAME'] ?? '') . ($person['HR_FNAME'] ?? '') . ' ' . ($person['HR_LNAME'] ?? '');
$department = $person['HR_DEPARTMENT_SUB_SUB_NAME'] ?? 'ไม่ระบุ';
$position = ($person['HR_POSITION_NAME'] ?? '') . ' ' . ($person['HR_LEVEL_NAME'] ?? '');
$formattedData[] = [
'id' => $person['HR_CID'] ?? '',
'text' => trim($fullName) . ' (' . trim($position) . ')',
'name' => trim($fullName),
'position' => trim($position)
];
}
$response = [
'status' => 'success',
'message' => 'พบข้อมูล',
'data' => $formattedData
];
}
} catch (\Exception $e) {
$response['message'] = $e->getMessage();
}
}
echo json_encode($response);
exit;
}
public function uploadSignature() {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['signature'])) {
$file = $_FILES['signature'];
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการอัปโหลดไฟล์ (Error Code: ' . $file['error'] . ')';
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
// Validate MIME type
$allowedTypes = ['image/jpeg', 'image/png'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $allowedTypes)) {
$_SESSION['error'] = 'กรุณาอัปโหลดไฟล์รูปภาพ JPG หรือ PNG เท่านั้น';
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
// Save as signature.png
$targetPath = APP_ROOT . '/public/img/signature.png';
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$_SESSION['success'] = 'อัปโหลดรูปลายเซ็นสำเร็จ';
} else {
$_SESSION['error'] = 'ไม่สามารถบันทึกไฟล์ได้';
}
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
}
public function uploadTaxSignature() {
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['tax_signature'])) {
$file = $_FILES['tax_signature'];
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
$_SESSION['error'] = 'เกิดข้อผิดพลาดในการอัปโหลดไฟล์ (Error Code: ' . $file['error'] . ')';
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
// Validate MIME type
$allowedTypes = ['image/jpeg', 'image/png'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mimeType, $allowedTypes)) {
$_SESSION['error'] = 'กรุณาอัปโหลดไฟล์รูปภาพ JPG หรือ PNG เท่านั้น';
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
// Save as tax_signature.png
$targetPath = APP_ROOT . '/public/img/tax_signature.png';
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$_SESSION['success'] = 'อัปโหลดรูปลายเซ็นสำหรับใบเสียภาษีสำเร็จ';
} else {
$_SESSION['error'] = 'ไม่สามารถบันทึกไฟล์ได้';
}
header('Location: ' . BASE_URL . '/settings/system');
exit;
}
}
// --- Officer Types ---
public function officerTypes() {
$model = new OfficerTypeModel();
$officerTypes = $model->getAll();
$this->view('settings/officer_types', [
'title' => 'จัดการประเภทเจ้าหน้าที่ | ' . APP_NAME,
'activeMenu' => 'setting_officer_types',
'officerTypes' => $officerTypes
]);
}
public function storeOfficerType() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$isActive = isset($_POST['is_active']) ? 1 : 0;
if (empty($name)) {
$_SESSION['error'] = 'กรุณากรอกชื่อประเภทเจ้าหน้าที่';
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
$model = new OfficerTypeModel();
if ($model->create($name, $isActive)) {
$_SESSION['success'] = 'เพิ่มประเภทเจ้าหน้าที่สำเร็จ';
} else {
$_SESSION['error'] = 'ไม่สามารถเพิ่มข้อมูลได้ (ชื่ออาจซ้ำ)';
}
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
}
public function updateOfficerType() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
$name = trim($_POST['name'] ?? '');
$isActive = isset($_POST['is_active']) ? 1 : 0;
if (!$id || empty($name)) {
$_SESSION['error'] = 'ข้อมูลไม่ครบถ้วน';
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
$model = new OfficerTypeModel();
if ($model->update($id, $name, $isActive)) {
$_SESSION['success'] = 'แก้ไขประเภทเจ้าหน้าที่สำเร็จ';
} else {
$_SESSION['error'] = 'ไม่สามารถแก้ไขข้อมูลได้';
}
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
}
public function toggleOfficerTypeStatus() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
$status = isset($_POST['status']) && $_POST['status'] == '1' ? 1 : 0;
if ($id) {
$model = new OfficerTypeModel();
$model->toggleActive($id, $status);
}
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
}
public function deleteOfficerType() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id'] ?? null;
if ($id) {
$model = new OfficerTypeModel();
$model->delete($id);
$_SESSION['success'] = 'ลบประเภทเจ้าหน้าที่สำเร็จ';
}
header('Location: ' . BASE_URL . '/settings/officer-types');
exit;
}
}
}
@@ -0,0 +1,139 @@
<?php
namespace app\Controllers;
use app\Models\UserModel;
use app\Models\UserLogModel;
class UserController extends Controller {
public function __construct() {
if (empty($_SESSION['user_id'])) {
$_SESSION['error'] = 'กรุณาเข้าสู่ระบบก่อนใช้งาน';
header('Location: ' . BASE_URL . '/login');
exit;
}
// Basic Role Check (Only Admin can manage users)
if ($_SESSION['role_id'] != 1) {
$_SESSION['error'] = 'คุณไม่มีสิทธิ์เข้าถึงหน้านี้';
header('Location: ' . BASE_URL . '/dashboard');
exit;
}
}
public function index() {
$userModel = new UserModel();
$users = $userModel->getAllUsers();
$roles = $userModel->getRoles();
$this->view('users/index', [
'title' => 'จัดการผู้ใช้งานระบบ | ' . APP_NAME,
'activeMenu' => 'users',
'users' => $users,
'roles' => $roles
]);
}
public function logs() {
$userModel = new UserModel();
$userLogModel = new UserLogModel();
$filters = [
'user_id' => $_GET['user_id'] ?? '',
'action' => $_GET['action'] ?? '',
'date_start' => $_GET['date_start'] ?? '',
'date_end' => $_GET['date_end'] ?? ''
];
$users = $userModel->getAllUsers();
$logs = $userLogModel->getLogs($filters);
$this->view('users/logs', [
'title' => 'ประวัติการใช้งานระบบ | ' . APP_NAME,
'activeMenu' => 'users',
'users' => $users,
'logs' => $logs,
'filters' => $filters
]);
}
public function store() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'username' => $_POST['username'] ?? '',
'password' => $_POST['password'] ?? '',
'role_id' => $_POST['role_id'] ?? 2,
'first_name' => $_POST['first_name'] ?? '',
'last_name' => $_POST['last_name'] ?? '',
'status' => isset($_POST['status']) ? 'active' : 'inactive'
];
$userModel = new UserModel();
try {
$userModel->createUser($data);
$logModel = new UserLogModel();
$logModel->logAction($_SESSION['user_id'], 'CREATE_USER', 'เพิ่มผู้ใช้งานใหม่ Username: ' . $data['username']);
$_SESSION['success'] = 'เพิ่มผู้ใช้งานสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถเพิ่มผู้ใช้งานได้ (Username อาจซ้ำ)';
}
header('Location: ' . BASE_URL . '/users');
exit;
}
}
public function update() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$data = [
'id' => $_POST['id'] ?? '',
'first_name' => $_POST['first_name'] ?? '',
'last_name' => $_POST['last_name'] ?? '',
'role_id' => $_POST['role_id'] ?? 2,
'password' => $_POST['password'] ?? ''
];
$userModel = new UserModel();
try {
$userModel->updateUser($data);
$logModel = new UserLogModel();
$logModel->logAction($_SESSION['user_id'], 'UPDATE_USER', 'แก้ไขข้อมูลผู้ใช้งาน ID: ' . $data['id']);
$_SESSION['success'] = 'แก้ไขข้อมูลผู้ใช้งานสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถแก้ไขข้อมูลได้';
}
header('Location: ' . BASE_URL . '/users');
exit;
}
}
public function toggle_status() {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$userId = $_POST['user_id'] ?? '';
$status = $_POST['status'] ?? 'inactive';
// Prevent locking out the only admin or self if needed (optional)
if ($userId == $_SESSION['user_id'] && $status == 'inactive') {
$_SESSION['error'] = 'ไม่สามารถปิดสิทธิ์บัญชีตัวเองได้';
} else {
$userModel = new UserModel();
try {
$userModel->updateStatus($userId, $status);
$logModel = new UserLogModel();
$logModel->logAction($_SESSION['user_id'], 'TOGGLE_STATUS', "เปลี่ยนสถานะผู้ใช้งาน ID: $userId เป็น $status");
$_SESSION['success'] = 'ปรับสถานะการใช้งานสำเร็จ';
} catch (\PDOException $e) {
$_SESSION['error'] = 'ไม่สามารถปรับสถานะได้';
}
}
header('Location: ' . BASE_URL . '/users');
exit;
}
}
}