feat(accounting): 實作公共事業費管理與會計支出報表功能
All checks were successful
Koori-ERP-Deploy-System / deploy-demo (push) Successful in 56s
Koori-ERP-Deploy-System / deploy-production (push) Has been skipped

This commit is contained in:
2026-01-20 09:44:05 +08:00
parent 8928a84ff9
commit 32c2612a5f
10 changed files with 1203 additions and 3 deletions

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers;
use App\Models\PurchaseOrder;
use App\Models\UtilityFee;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Illuminate\Support\Carbon;
class AccountingReportController extends Controller
{
public function index(Request $request)
{
$dateStart = $request->input('date_start', Carbon::now()->startOfMonth()->toDateString());
$dateEnd = $request->input('date_end', Carbon::now()->endOfMonth()->toDateString());
// 1. Get Purchase Orders (Completed or Received that are ready for accounting)
$purchaseOrders = PurchaseOrder::with(['vendor'])
->whereIn('status', ['received', 'completed'])
->whereBetween('created_at', [$dateStart . ' 00:00:00', $dateEnd . ' 23:59:59'])
->get()
->map(function ($po) {
return [
'id' => 'PO-' . $po->id,
'date' => $po->created_at->toDateString(),
'source' => '採購單',
'category' => '進貨支出',
'item' => $po->vendor->name ?? '未知廠商',
'reference' => $po->code,
'invoice_number' => $po->invoice_number,
'amount' => $po->grand_total,
];
});
// 2. Get Utility Fees
$utilityFees = UtilityFee::whereBetween('transaction_date', [$dateStart, $dateEnd])
->get()
->map(function ($fee) {
return [
'id' => 'UF-' . $fee->id,
'date' => $fee->transaction_date,
'source' => '公共事業費',
'category' => $fee->category,
'item' => $fee->description ?: $fee->category,
'reference' => '-',
'invoice_number' => $fee->invoice_number,
'amount' => $fee->amount,
];
});
// Combine and Sort
$allRecords = $purchaseOrders->concat($utilityFees)
->sortByDesc('date')
->values();
$summary = [
'total_amount' => $allRecords->sum('amount'),
'purchase_total' => $purchaseOrders->sum('amount'),
'utility_total' => $utilityFees->sum('amount'),
'record_count' => $allRecords->count(),
];
return Inertia::render('Accounting/Report', [
'records' => $allRecords,
'summary' => $summary,
'filters' => [
'date_start' => $dateStart,
'date_end' => $dateEnd,
],
]);
}
public function export(Request $request)
{
$dateStart = $request->input('date_start', Carbon::now()->startOfMonth()->toDateString());
$dateEnd = $request->input('date_end', Carbon::now()->endOfMonth()->toDateString());
$purchaseOrders = PurchaseOrder::with(['vendor'])
->whereIn('status', ['received', 'completed'])
->whereBetween('created_at', [$dateStart . ' 00:00:00', $dateEnd . ' 23:59:59'])
->get();
$utilityFees = UtilityFee::whereBetween('transaction_date', [$dateStart, $dateEnd])->get();
$allRecords = collect();
foreach ($purchaseOrders as $po) {
$allRecords->push([
$po->created_at->toDateString(),
'採購單',
'進貨支出',
$po->vendor->name ?? '',
$po->code,
$po->invoice_number,
$po->grand_total,
]);
}
foreach ($utilityFees as $fee) {
$allRecords->push([
$fee->transaction_date,
'公共事業費',
$fee->category,
$fee->description,
'-',
$fee->invoice_number,
$fee->amount,
]);
}
$allRecords = $allRecords->sortByDesc(0);
$filename = "accounting_report_{$dateStart}_{$dateEnd}.csv";
$headers = [
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => "attachment; filename=\"{$filename}\"",
];
$callback = function () use ($allRecords) {
$file = fopen('php://output', 'w');
// BOM for Excel compatibility with UTF-8
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($file, ['日期', '來源', '類別', '項目', '參考單號', '發票號碼', '金額']);
foreach ($allRecords as $row) {
fputcsv($file, $row);
}
fclose($file);
};
return response()->stream($callback, 200, $headers);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers;
use App\Models\UtilityFee;
use Illuminate\Http\Request;
use Inertia\Inertia;
class UtilityFeeController extends Controller
{
public function index(Request $request)
{
$query = UtilityFee::query();
// Search
if ($request->has('search')) {
$search = $request->input('search');
$query->where(function($q) use ($search) {
$q->where('category', 'like', "%{$search}%")
->orWhere('invoice_number', 'like', "%{$search}%")
->orWhere('description', 'like', "%{$search}%");
});
}
// Filtering
if ($request->filled('category') && $request->input('category') !== 'all') {
$query->where('category', $request->input('category'));
}
if ($request->filled('date_start')) {
$query->where('transaction_date', '>=', $request->input('date_start'));
}
if ($request->filled('date_end')) {
$query->where('transaction_date', '<=', $request->input('date_end'));
}
// Sorting
$sortField = $request->input('sort_field', 'transaction_date');
$sortDirection = $request->input('sort_direction', 'desc');
$query->orderBy($sortField, $sortDirection);
$fees = $query->paginate($request->input('per_page', 15))->withQueryString();
$availableCategories = UtilityFee::distinct()->pluck('category');
return Inertia::render('UtilityFee/Index', [
'fees' => $fees,
'availableCategories' => $availableCategories,
'filters' => $request->only(['search', 'category', 'date_start', 'date_end', 'sort_field', 'sort_direction']),
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'transaction_date' => 'required|date',
'category' => 'required|string|max:255',
'amount' => 'required|numeric|min:0',
'invoice_number' => 'nullable|string|max:255',
'description' => 'nullable|string',
]);
UtilityFee::create($validated);
return redirect()->back();
}
public function update(Request $request, UtilityFee $utility_fee)
{
$validated = $request->validate([
'transaction_date' => 'required|date',
'category' => 'required|string|max:255',
'amount' => 'required|numeric|min:0',
'invoice_number' => 'nullable|string|max:255',
'description' => 'nullable|string',
]);
$utility_fee->update($validated);
return redirect()->back();
}
public function destroy(UtilityFee $utility_fee)
{
$utility_fee->delete();
return redirect()->back();
}
}