Add membership fee system with disability discount and fix document permissions

Features:
- Implement two fee types: entrance fee and annual fee (both NT$1,000)
- Add 50% discount for disability certificate holders
- Add disability certificate upload in member profile
- Integrate disability verification into cashier approval workflow
- Add membership fee settings in system admin

Document permissions:
- Fix hard-coded role logic in Document model
- Use permission-based authorization instead of role checks

Additional features:
- Add announcements, general ledger, and trial balance modules
- Add income management and accounting entries
- Add comprehensive test suite with factories
- Update UI translations to Traditional Chinese

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-01 09:56:01 +08:00
parent 83ce1f7fc8
commit 642b879dd4
207 changed files with 19487 additions and 3048 deletions

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AccountingEntry;
use App\Models\ChartOfAccount;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class TrialBalanceController extends Controller
{
/**
* Display the trial balance
*/
public function index(Request $request)
{
$startDate = $request->input('start_date', now()->startOfYear()->format('Y-m-d'));
$endDate = $request->input('end_date', now()->format('Y-m-d'));
// Get all active accounts with their balances
$accounts = ChartOfAccount::where('is_active', true)
->orderBy('account_code')
->get()
->map(function ($account) use ($startDate, $endDate) {
// Get debit and credit totals for this account
$debitTotal = AccountingEntry::where('chart_of_account_id', $account->id)
->whereBetween('entry_date', [$startDate, $endDate])
->where('entry_type', AccountingEntry::ENTRY_TYPE_DEBIT)
->sum('amount');
$creditTotal = AccountingEntry::where('chart_of_account_id', $account->id)
->whereBetween('entry_date', [$startDate, $endDate])
->where('entry_type', AccountingEntry::ENTRY_TYPE_CREDIT)
->sum('amount');
// Only include accounts with activity
if ($debitTotal == 0 && $creditTotal == 0) {
return null;
}
return [
'account' => $account,
'debit_total' => $debitTotal,
'credit_total' => $creditTotal,
];
})
->filter() // Remove null entries
->values();
// Calculate grand totals
$grandDebitTotal = $accounts->sum('debit_total');
$grandCreditTotal = $accounts->sum('credit_total');
// Check if balanced
$isBalanced = bccomp((string)$grandDebitTotal, (string)$grandCreditTotal, 2) === 0;
$difference = $grandDebitTotal - $grandCreditTotal;
// Group accounts by type
$accountsByType = $accounts->groupBy(function ($item) {
return $item['account']->account_type;
});
return view('admin.trial-balance.index', compact(
'accounts',
'accountsByType',
'startDate',
'endDate',
'grandDebitTotal',
'grandCreditTotal',
'isBalanced',
'difference'
));
}
}