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:
@@ -22,6 +22,11 @@ class Member extends Model
|
||||
const TYPE_LIFETIME = 'lifetime';
|
||||
const TYPE_STUDENT = 'student';
|
||||
|
||||
// Disability certificate status constants
|
||||
const DISABILITY_STATUS_PENDING = 'pending';
|
||||
const DISABILITY_STATUS_APPROVED = 'approved';
|
||||
const DISABILITY_STATUS_REJECTED = 'rejected';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'full_name',
|
||||
@@ -39,11 +44,17 @@ class Member extends Model
|
||||
'membership_expires_at',
|
||||
'membership_status',
|
||||
'membership_type',
|
||||
'disability_certificate_path',
|
||||
'disability_certificate_status',
|
||||
'disability_verified_by',
|
||||
'disability_verified_at',
|
||||
'disability_rejection_reason',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'membership_started_at' => 'date',
|
||||
'membership_expires_at' => 'date',
|
||||
'disability_verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $appends = ['national_id'];
|
||||
@@ -58,6 +69,37 @@ class Member extends Model
|
||||
return $this->hasMany(MembershipPayment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 關聯的收入記錄
|
||||
*/
|
||||
public function incomes()
|
||||
{
|
||||
return $this->hasMany(Income::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會員的會費收入記錄
|
||||
*/
|
||||
public function getMembershipFeeIncomes()
|
||||
{
|
||||
return $this->incomes()
|
||||
->whereIn('income_type', [
|
||||
Income::TYPE_MEMBERSHIP_FEE,
|
||||
Income::TYPE_ENTRANCE_FEE
|
||||
])
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會員的總收入金額
|
||||
*/
|
||||
public function getTotalIncomeAttribute(): float
|
||||
{
|
||||
return $this->incomes()
|
||||
->where('status', Income::STATUS_CONFIRMED)
|
||||
->sum('amount');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decrypted national ID
|
||||
*/
|
||||
@@ -203,4 +245,120 @@ class Member extends Model
|
||||
// Can submit if pending status and no pending payment
|
||||
return $this->isPending() && !$this->getPendingPayment();
|
||||
}
|
||||
|
||||
// ========== 身心障礙相關 ==========
|
||||
|
||||
/**
|
||||
* 身心障礙手冊審核人
|
||||
*/
|
||||
public function disabilityVerifiedBy()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'disability_verified_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有上傳身心障礙手冊
|
||||
*/
|
||||
public function hasDisabilityCertificate(): bool
|
||||
{
|
||||
return !empty($this->disability_certificate_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否待審核
|
||||
*/
|
||||
public function isDisabilityPending(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否已通過審核
|
||||
*/
|
||||
public function hasApprovedDisability(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_APPROVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身心障礙手冊是否被駁回
|
||||
*/
|
||||
public function isDisabilityRejected(): bool
|
||||
{
|
||||
return $this->disability_certificate_status === self::DISABILITY_STATUS_REJECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙狀態標籤
|
||||
*/
|
||||
public function getDisabilityStatusLabelAttribute(): string
|
||||
{
|
||||
if (!$this->hasDisabilityCertificate()) {
|
||||
return '未上傳';
|
||||
}
|
||||
|
||||
return match ($this->disability_certificate_status) {
|
||||
self::DISABILITY_STATUS_PENDING => '審核中',
|
||||
self::DISABILITY_STATUS_APPROVED => '已通過',
|
||||
self::DISABILITY_STATUS_REJECTED => '已駁回',
|
||||
default => '未知',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得身心障礙狀態的 Badge 樣式
|
||||
*/
|
||||
public function getDisabilityStatusBadgeAttribute(): string
|
||||
{
|
||||
if (!$this->hasDisabilityCertificate()) {
|
||||
return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200';
|
||||
}
|
||||
|
||||
return match ($this->disability_certificate_status) {
|
||||
self::DISABILITY_STATUS_PENDING => 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
||||
self::DISABILITY_STATUS_APPROVED => 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
self::DISABILITY_STATUS_REJECTED => 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
||||
default => 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 審核通過身心障礙手冊
|
||||
*/
|
||||
public function approveDisabilityCertificate(User $verifier): void
|
||||
{
|
||||
$this->update([
|
||||
'disability_certificate_status' => self::DISABILITY_STATUS_APPROVED,
|
||||
'disability_verified_by' => $verifier->id,
|
||||
'disability_verified_at' => now(),
|
||||
'disability_rejection_reason' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 駁回身心障礙手冊
|
||||
*/
|
||||
public function rejectDisabilityCertificate(User $verifier, string $reason): void
|
||||
{
|
||||
$this->update([
|
||||
'disability_certificate_status' => self::DISABILITY_STATUS_REJECTED,
|
||||
'disability_verified_by' => $verifier->id,
|
||||
'disability_verified_at' => now(),
|
||||
'disability_rejection_reason' => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判斷下一次應繳哪種會費
|
||||
*/
|
||||
public function getNextFeeType(): string
|
||||
{
|
||||
// 新會員(從未啟用過)= 入會會費
|
||||
if ($this->membership_started_at === null) {
|
||||
return MembershipPayment::FEE_TYPE_ENTRANCE;
|
||||
}
|
||||
|
||||
// 已有會籍 = 常年會費
|
||||
return MembershipPayment::FEE_TYPE_ANNUAL;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user