475 lines
18 KiB
PHP
475 lines
18 KiB
PHP
<?php
|
|
namespace app\controllers;
|
|
|
|
use core\base\Controller;
|
|
use app\models\FinanceRecord;
|
|
use app\models\ReceivablePayable;
|
|
|
|
/**
|
|
* 财务管理控制器
|
|
* 访问权限:超级管理员 或 财务类目管理员
|
|
*/
|
|
class FinanceController extends Controller
|
|
{
|
|
private function checkCategoryAccess()
|
|
{
|
|
$this->checkLogin();
|
|
$user = $this->getCurrentUser();
|
|
|
|
if ($user['role'] === self::ROLE_SUPER_ADMIN) {
|
|
return $user;
|
|
}
|
|
if ($user['role'] === self::ROLE_ADMIN && ($user['category'] ?? '') === self::CATEGORY_FINANCE) {
|
|
return $user;
|
|
}
|
|
header('Location: ' . BASE_URL . '/Front/index');
|
|
exit;
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
|
|
$financeRecord = new FinanceRecord();
|
|
$receivablePayable = new ReceivablePayable();
|
|
|
|
// 本月收支汇总
|
|
$monthStart = date('Y-m-01');
|
|
$monthEnd = date('Y-m-t');
|
|
$summary = $financeRecord->getSummary($monthStart, $monthEnd);
|
|
$incomeTotal = 0;
|
|
$expenseTotal = 0;
|
|
foreach ($summary as $row) {
|
|
if ($row['type'] === 'income') $incomeTotal = (float)$row['total'];
|
|
if ($row['type'] === 'expense') $expenseTotal = (float)$row['total'];
|
|
}
|
|
|
|
// 应收应付统计
|
|
$receivables = $receivablePayable->getByType('receivable');
|
|
$payables = $receivablePayable->getByType('payable');
|
|
$receivableTotal = 0;
|
|
$payableTotal = 0;
|
|
foreach ($receivables as $r) {
|
|
$receivableTotal += (float)($r['amount'] ?? 0);
|
|
}
|
|
foreach ($payables as $p) {
|
|
$payableTotal += (float)($p['amount'] ?? 0);
|
|
}
|
|
|
|
$this->assign('title', '财务概览');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'finance_dashboard');
|
|
$this->assign('incomeTotal', $incomeTotal);
|
|
$this->assign('expenseTotal', $expenseTotal);
|
|
$this->assign('profit', $incomeTotal - $expenseTotal);
|
|
$this->assign('receivableTotal', $receivableTotal);
|
|
$this->assign('payableTotal', $payableTotal);
|
|
$this->assign('recordCount', $financeRecord->getCount());
|
|
$this->render();
|
|
}
|
|
|
|
// ========== 账务管理 ==========
|
|
public function accounts()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$financeRecord = new FinanceRecord();
|
|
|
|
$typeFilter = $_GET['type'] ?? '';
|
|
$startDate = $_GET['start_date'] ?? date('Y-m-01');
|
|
$endDate = $_GET['end_date'] ?? date('Y-m-d');
|
|
|
|
if ($typeFilter || $startDate || $endDate) {
|
|
$records = $financeRecord->getByDateRange($startDate, $endDate, $typeFilter);
|
|
} else {
|
|
$records = $financeRecord->getAll();
|
|
}
|
|
|
|
$this->assign('title', '账务管理');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('records', $records);
|
|
$this->assign('typeFilter', $typeFilter);
|
|
$this->assign('startDate', $startDate);
|
|
$this->assign('endDate', $endDate);
|
|
$this->render();
|
|
}
|
|
|
|
public function accountsAdd()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'record_no' => trim($_POST['record_no'] ?? ''),
|
|
'type' => trim($_POST['type'] ?? 'expense'),
|
|
'category' => trim($_POST['category'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'record_date' => trim($_POST['record_date'] ?? date('Y-m-d')),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'related_party' => trim($_POST['related_party'] ?? ''),
|
|
'payment_method' => trim($_POST['payment_method'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
'operator' => $user['emp_name'],
|
|
];
|
|
if (empty($data['description'])) {
|
|
$this->assign('error', '摘要不能为空');
|
|
$this->assign('title', '添加账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
if ($data['amount'] <= 0) {
|
|
$this->assign('error', '金额必须大于0');
|
|
$this->assign('title', '添加账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
try {
|
|
$financeRecord = new FinanceRecord();
|
|
$financeRecord->add($data);
|
|
header('Location: ' . BASE_URL . '/Finance/accounts');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[accountsAdd] ' . $e->getMessage());
|
|
$this->assign('error', '添加失败:' . $e->getMessage());
|
|
$this->assign('title', '添加账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '添加账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function accountsEdit()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
$financeRecord = new FinanceRecord();
|
|
$record = $financeRecord->getById($id);
|
|
if (!$record) exit('记录不存在');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'record_no' => trim($_POST['record_no'] ?? ''),
|
|
'type' => trim($_POST['type'] ?? 'expense'),
|
|
'category' => trim($_POST['category'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'record_date' => trim($_POST['record_date'] ?? ''),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'related_party' => trim($_POST['related_party'] ?? ''),
|
|
'payment_method' => trim($_POST['payment_method'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
];
|
|
try {
|
|
$financeRecord->where(['id = :id'], [':id' => $id])->update($data);
|
|
header('Location: ' . BASE_URL . '/Finance/accounts');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[accountsEdit] ' . $e->getMessage());
|
|
$this->assign('error', '更新失败:' . $e->getMessage());
|
|
$this->assign('title', '编辑账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '编辑账务记录');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'accounts');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function accountsDelete()
|
|
{
|
|
$this->checkLogin();
|
|
$this->requireMethod('post');
|
|
$this->csrfVerify();
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
$financeRecord = new FinanceRecord();
|
|
$financeRecord->delete($id);
|
|
header('Location: ' . BASE_URL . '/Finance/accounts');
|
|
exit;
|
|
}
|
|
|
|
// ========== 应收账款 ==========
|
|
public function receivable()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$receivablePayable = new ReceivablePayable();
|
|
$records = $receivablePayable->getByType('receivable');
|
|
|
|
$this->assign('title', '应收账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'receivable');
|
|
$this->assign('records', $records);
|
|
$this->render();
|
|
}
|
|
|
|
public function receivableAdd()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'type' => 'receivable',
|
|
'party_name' => trim($_POST['party_name'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'paid_amount' => (float)($_POST['paid_amount'] ?? 0),
|
|
'due_date' => trim($_POST['due_date'] ?? ''),
|
|
'status' => trim($_POST['status'] ?? 'pending'),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
'operator' => $user['emp_name'],
|
|
];
|
|
try {
|
|
$receivablePayable = new ReceivablePayable();
|
|
$receivablePayable->add($data);
|
|
header('Location: ' . BASE_URL . '/Finance/receivable');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[receivableAdd] ' . $e->getMessage());
|
|
$this->assign('error', '添加失败:' . $e->getMessage());
|
|
$this->assign('title', '添加应收账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'receivable');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '添加应收账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'receivable');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function receivableEdit()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
$receivablePayable = new ReceivablePayable();
|
|
$record = $receivablePayable->getById($id);
|
|
if (!$record) exit('记录不存在');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'party_name' => trim($_POST['party_name'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'paid_amount' => (float)($_POST['paid_amount'] ?? 0),
|
|
'due_date' => trim($_POST['due_date'] ?? ''),
|
|
'status' => trim($_POST['status'] ?? 'pending'),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
];
|
|
try {
|
|
$receivablePayable->where(['id = :id'], [':id' => $id])->update($data);
|
|
header('Location: ' . BASE_URL . '/Finance/receivable');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[receivableEdit] ' . $e->getMessage());
|
|
$this->assign('error', '更新失败:' . $e->getMessage());
|
|
$this->assign('title', '编辑应收账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'receivable');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '编辑应收账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'receivable');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function receivableDelete()
|
|
{
|
|
$this->checkLogin();
|
|
$this->requireMethod('post');
|
|
$this->csrfVerify();
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
$receivablePayable = new ReceivablePayable();
|
|
$receivablePayable->delete($id);
|
|
header('Location: ' . BASE_URL . '/Finance/receivable');
|
|
exit;
|
|
}
|
|
|
|
// ========== 应付账款 ==========
|
|
public function payable()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$receivablePayable = new ReceivablePayable();
|
|
$records = $receivablePayable->getByType('payable');
|
|
|
|
$this->assign('title', '应付账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'payable');
|
|
$this->assign('records', $records);
|
|
$this->render();
|
|
}
|
|
|
|
public function payableAdd()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'type' => 'payable',
|
|
'party_name' => trim($_POST['party_name'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'paid_amount' => (float)($_POST['paid_amount'] ?? 0),
|
|
'due_date' => trim($_POST['due_date'] ?? ''),
|
|
'status' => trim($_POST['status'] ?? 'pending'),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
'operator' => $user['emp_name'],
|
|
];
|
|
try {
|
|
$receivablePayable = new ReceivablePayable();
|
|
$receivablePayable->add($data);
|
|
header('Location: ' . BASE_URL . '/Finance/payable');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[payableAdd] ' . $e->getMessage());
|
|
$this->assign('error', '添加失败:' . $e->getMessage());
|
|
$this->assign('title', '添加应付账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'payable');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '添加应付账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'payable');
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function payableEdit()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$id = (int)($_GET['id'] ?? 0);
|
|
$receivablePayable = new ReceivablePayable();
|
|
$record = $receivablePayable->getById($id);
|
|
if (!$record) exit('记录不存在');
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->csrfVerify();
|
|
$data = [
|
|
'party_name' => trim($_POST['party_name'] ?? ''),
|
|
'amount' => (float)($_POST['amount'] ?? 0),
|
|
'paid_amount' => (float)($_POST['paid_amount'] ?? 0),
|
|
'due_date' => trim($_POST['due_date'] ?? ''),
|
|
'status' => trim($_POST['status'] ?? 'pending'),
|
|
'description' => trim($_POST['description'] ?? ''),
|
|
'remark' => trim($_POST['remark'] ?? ''),
|
|
];
|
|
try {
|
|
$receivablePayable->where(['id = :id'], [':id' => $id])->update($data);
|
|
header('Location: ' . BASE_URL . '/Finance/payable');
|
|
exit;
|
|
} catch (\Throwable $e) {
|
|
error_log('[payableEdit] ' . $e->getMessage());
|
|
$this->assign('error', '更新失败:' . $e->getMessage());
|
|
$this->assign('title', '编辑应付账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'payable');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
return;
|
|
}
|
|
}
|
|
|
|
$this->assign('title', '编辑应付账款');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'payable');
|
|
$this->assign('record', $record);
|
|
$this->assign('csrfToken', $this->csrfToken());
|
|
$this->render();
|
|
}
|
|
|
|
public function payableDelete()
|
|
{
|
|
$this->checkLogin();
|
|
$this->requireMethod('post');
|
|
$this->csrfVerify();
|
|
$id = (int)($_POST['id'] ?? 0);
|
|
$receivablePayable = new ReceivablePayable();
|
|
$receivablePayable->delete($id);
|
|
header('Location: ' . BASE_URL . '/Finance/payable');
|
|
exit;
|
|
}
|
|
|
|
// ========== 财务报表 ==========
|
|
public function report()
|
|
{
|
|
$user = $this->checkCategoryAccess();
|
|
$financeRecord = new FinanceRecord();
|
|
|
|
$month = $_GET['month'] ?? date('Y-m');
|
|
$startDate = $month . '-01';
|
|
$endDate = date('Y-m-t', strtotime($startDate));
|
|
|
|
$summary = $financeRecord->getSummary($startDate, $endDate);
|
|
$incomeTotal = 0;
|
|
$expenseTotal = 0;
|
|
foreach ($summary as $row) {
|
|
if ($row['type'] === 'income') $incomeTotal = (float)$row['total'];
|
|
if ($row['type'] === 'expense') $expenseTotal = (float)$row['total'];
|
|
}
|
|
|
|
// 按类别统计
|
|
$records = $financeRecord->getByDateRange($startDate, $endDate);
|
|
$categorySummary = [];
|
|
foreach ($records as $r) {
|
|
$cat = $r['category'] ?: '未分类';
|
|
$type = $r['type'];
|
|
if (!isset($categorySummary[$cat])) {
|
|
$categorySummary[$cat] = ['income' => 0, 'expense' => 0];
|
|
}
|
|
$categorySummary[$cat][$type] += (float)$r['amount'];
|
|
}
|
|
|
|
$this->assign('title', '财务报表');
|
|
$this->assign('user', $user);
|
|
$this->assign('activeMenu', 'report');
|
|
$this->assign('incomeTotal', $incomeTotal);
|
|
$this->assign('expenseTotal', $expenseTotal);
|
|
$this->assign('profit', $incomeTotal - $expenseTotal);
|
|
$this->assign('categorySummary', $categorySummary);
|
|
$this->assign('month', $month);
|
|
$this->render();
|
|
}
|
|
}
|