Files
sam-manage/app/Models/HR/BusinessIncomePayment.php
김보곤 2ac4c188d5 feat: [hr] 사업소득자 임금대장 동적 행 입력 리디자인
- earner 고정 행 → 동적 행 추가/삭제 구조로 변경
- 상호/성명 datalist 콤보박스 (드롭다운 선택 + 직접 입력)
- display_name/business_reg_number 컬럼 직접 저장
- bulkSave: payment_id 기반 upsert + 미제출 draft 자동 삭제
- confirmed/paid 행 수정/삭제 불가 유지
- 엑셀 내보내기 display_name 직접 사용으로 단순화
2026-03-03 14:21:06 +09:00

195 lines
5.3 KiB
PHP

<?php
namespace App\Models\HR;
use App\Models\User;
use App\Traits\ModelTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class BusinessIncomePayment extends Model
{
use ModelTrait, SoftDeletes;
protected $table = 'business_income_payments';
protected $fillable = [
'tenant_id',
'user_id',
'display_name',
'business_reg_number',
'pay_year',
'pay_month',
'service_content',
'gross_amount',
'income_tax',
'local_income_tax',
'total_deductions',
'net_amount',
'payment_date',
'note',
'status',
'confirmed_at',
'confirmed_by',
'paid_at',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'tenant_id' => 'int',
'user_id' => 'int',
'pay_year' => 'int',
'pay_month' => 'int',
'gross_amount' => 'decimal:0',
'income_tax' => 'decimal:0',
'local_income_tax' => 'decimal:0',
'total_deductions' => 'decimal:0',
'net_amount' => 'decimal:0',
'payment_date' => 'date',
'confirmed_at' => 'datetime',
'paid_at' => 'datetime',
];
protected $attributes = [
'status' => 'draft',
];
// =========================================================================
// 상수
// =========================================================================
public const STATUS_DRAFT = 'draft';
public const STATUS_CONFIRMED = 'confirmed';
public const STATUS_PAID = 'paid';
public const STATUS_MAP = [
'draft' => '작성중',
'confirmed' => '확정',
'paid' => '지급완료',
];
public const STATUS_COLORS = [
'draft' => 'amber',
'confirmed' => 'blue',
'paid' => 'emerald',
];
/** 소득세율 3% */
public const INCOME_TAX_RATE = 0.03;
/** 지방소득세율 0.3% */
public const LOCAL_TAX_RATE = 0.003;
// =========================================================================
// 관계 정의
// =========================================================================
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function confirmer(): BelongsTo
{
return $this->belongsTo(User::class, 'confirmed_by');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
// =========================================================================
// Accessor
// =========================================================================
public function getStatusLabelAttribute(): string
{
return self::STATUS_MAP[$this->status] ?? $this->status;
}
public function getStatusColorAttribute(): string
{
return self::STATUS_COLORS[$this->status] ?? 'gray';
}
public function getPeriodLabelAttribute(): string
{
return sprintf('%d년 %d월', $this->pay_year, $this->pay_month);
}
// =========================================================================
// 상태 헬퍼
// =========================================================================
public function isEditable(): bool
{
return $this->status === self::STATUS_DRAFT;
}
public function isConfirmable(): bool
{
return $this->status === self::STATUS_DRAFT;
}
public function isPayable(): bool
{
return $this->status === self::STATUS_CONFIRMED;
}
public function isDeletable(): bool
{
return $this->status === self::STATUS_DRAFT;
}
// =========================================================================
// 세금 계산 (정적)
// =========================================================================
/**
* 지급총액으로 세금/공제/실지급액 계산
*
* @return array{income_tax: int, local_income_tax: int, total_deductions: int, net_amount: int}
*/
public static function calculateTax(float $grossAmount): array
{
$incomeTax = (int) (floor($grossAmount * self::INCOME_TAX_RATE / 10) * 10);
$localIncomeTax = (int) (floor($grossAmount * self::LOCAL_TAX_RATE / 10) * 10);
$totalDeductions = $incomeTax + $localIncomeTax;
$netAmount = (int) max(0, $grossAmount - $totalDeductions);
return [
'income_tax' => $incomeTax,
'local_income_tax' => $localIncomeTax,
'total_deductions' => $totalDeductions,
'net_amount' => $netAmount,
];
}
// =========================================================================
// 스코프
// =========================================================================
public function scopeForTenant($query, ?int $tenantId = null)
{
$tenantId = $tenantId ?? session('selected_tenant_id', 1);
return $query->where($this->table.'.tenant_id', $tenantId);
}
public function scopeForPeriod($query, int $year, int $month)
{
return $query->where('pay_year', $year)->where('pay_month', $month);
}
public function scopeWithStatus($query, string $status)
{
return $query->where('status', $status);
}
}