Initial financial project import
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
function normalize_card_kind(array $account): ?string
|
||||
{
|
||||
$kind = strtolower(trim((string)($account['card_kind'] ?? '')));
|
||||
|
||||
if ($kind === 'credit') {
|
||||
return 'credit';
|
||||
}
|
||||
|
||||
if ($kind === 'check') {
|
||||
return 'check';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function safe_date_ts(string $date): ?int
|
||||
{
|
||||
$ts = strtotime($date);
|
||||
return $ts === false ? null : $ts;
|
||||
}
|
||||
|
||||
function card_month_day_date(string $billingYearMonth, int $monthOffset, int $day): ?DateTime
|
||||
{
|
||||
if (!preg_match('/^\d{4}-\d{2}$/', $billingYearMonth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($day < 1 || $day > 31) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$base = new DateTime($billingYearMonth . '-01');
|
||||
|
||||
if ($monthOffset !== 0) {
|
||||
$base->modify(($monthOffset > 0 ? '+' : '') . $monthOffset . ' month');
|
||||
}
|
||||
|
||||
$lastDay = (int)$base->format('t');
|
||||
$realDay = min($day, $lastDay);
|
||||
|
||||
$base->setDate(
|
||||
(int)$base->format('Y'),
|
||||
(int)$base->format('m'),
|
||||
$realDay
|
||||
);
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
function account_has_statement_period(array $account): bool
|
||||
{
|
||||
return isset(
|
||||
$account['statement_start_month_offset'],
|
||||
$account['statement_start_day'],
|
||||
$account['statement_end_month_offset'],
|
||||
$account['statement_end_day']
|
||||
)
|
||||
&& $account['statement_start_month_offset'] !== null
|
||||
&& $account['statement_start_day'] !== null
|
||||
&& $account['statement_end_month_offset'] !== null
|
||||
&& $account['statement_end_day'] !== null
|
||||
&& (int)$account['statement_start_day'] >= 1
|
||||
&& (int)$account['statement_start_day'] <= 31
|
||||
&& (int)$account['statement_end_day'] >= 1
|
||||
&& (int)$account['statement_end_day'] <= 31;
|
||||
}
|
||||
|
||||
function transaction_date_in_card_statement_month(array $account, string $transactionDate, string $billingYearMonth): bool
|
||||
{
|
||||
$txTs = safe_date_ts($transactionDate);
|
||||
if ($txTs === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!account_has_statement_period($account)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start = card_month_day_date(
|
||||
$billingYearMonth,
|
||||
(int)$account['statement_start_month_offset'],
|
||||
(int)$account['statement_start_day']
|
||||
);
|
||||
|
||||
$end = card_month_day_date(
|
||||
$billingYearMonth,
|
||||
(int)$account['statement_end_month_offset'],
|
||||
(int)$account['statement_end_day']
|
||||
);
|
||||
|
||||
if (!$start || !$end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tx = new DateTime(date('Y-m-d', $txTs));
|
||||
|
||||
return $tx >= $start && $tx <= $end;
|
||||
}
|
||||
|
||||
function get_card_billing_year_month_by_statement_period(array $account, string $transactionDate): ?string
|
||||
{
|
||||
$txTs = safe_date_ts($transactionDate);
|
||||
if ($txTs === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!account_has_statement_period($account)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tx = new DateTime(date('Y-m-d', $txTs));
|
||||
|
||||
/*
|
||||
* 청구월 기준 사용기간 예:
|
||||
* 2026-05 청구월 = 2026-04-11 ~ 2026-05-10
|
||||
*
|
||||
* 거래일 주변의 청구월만 검사하면 됨.
|
||||
* 전월/당월/익월/익익월까지 여유 있게 검사.
|
||||
*/
|
||||
$base = new DateTime($tx->format('Y-m-01'));
|
||||
|
||||
for ($i = -1; $i <= 2; $i++) {
|
||||
$candidate = clone $base;
|
||||
if ($i !== 0) {
|
||||
$candidate->modify(($i > 0 ? '+' : '') . $i . ' month');
|
||||
}
|
||||
|
||||
$candidateYm = $candidate->format('Y-m');
|
||||
|
||||
if (transaction_date_in_card_statement_month($account, $transactionDate, $candidateYm)) {
|
||||
return $candidateYm;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_card_billing_year_month(array $account, string $transactionDate): ?string
|
||||
{
|
||||
if (($account['account_type'] ?? '') !== 'card') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ts = safe_date_ts($transactionDate);
|
||||
if ($ts === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cardKind = normalize_card_kind($account);
|
||||
|
||||
// 체크카드는 즉시형으로 보고 거래월 그대로
|
||||
if ($cardKind === 'check') {
|
||||
return date('Y-m', $ts);
|
||||
}
|
||||
|
||||
// 신용카드인데 신용공여기간 계산을 안 쓰면 거래월 그대로
|
||||
if (empty($account['use_credit_grace_period'])) {
|
||||
return date('Y-m', $ts);
|
||||
}
|
||||
|
||||
// 신규 방식: 카드사별 실제 사용기간 설정 우선
|
||||
$statementYm = get_card_billing_year_month_by_statement_period($account, $transactionDate);
|
||||
if ($statementYm !== null) {
|
||||
return $statementYm;
|
||||
}
|
||||
|
||||
// fallback: 기존 billing_day 단순 방식
|
||||
$billingDay = (int)($account['billing_day'] ?? 0);
|
||||
if ($billingDay <= 0 || $billingDay > 31) {
|
||||
return date('Y-m', $ts);
|
||||
}
|
||||
|
||||
$dt = new DateTime(date('Y-m-d', $ts));
|
||||
$day = (int)$dt->format('d');
|
||||
|
||||
if ($day <= $billingDay) {
|
||||
return $dt->format('Y-m');
|
||||
}
|
||||
|
||||
$dt->modify('first day of next month');
|
||||
return $dt->format('Y-m');
|
||||
}
|
||||
|
||||
function get_card_payment_date(array $account, string $billingYearMonth): ?string
|
||||
{
|
||||
if (($account['account_type'] ?? '') !== 'card') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('/^\d{4}-\d{2}$/', $billingYearMonth)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paymentDay = (int)($account['payment_day'] ?? 0);
|
||||
if ($paymentDay <= 0 || $paymentDay > 31) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$year, $month] = explode('-', $billingYearMonth);
|
||||
$year = (int)$year;
|
||||
$month = (int)$month;
|
||||
|
||||
$firstDay = new DateTime(sprintf('%04d-%02d-01', $year, $month));
|
||||
$lastDay = (int)$firstDay->format('t');
|
||||
$day = min($paymentDay, $lastDay);
|
||||
|
||||
$firstDay->setDate($year, $month, $day);
|
||||
return $firstDay->format('Y-m-d');
|
||||
}
|
||||
|
||||
function get_card_statement_period_label(array $account): ?string
|
||||
{
|
||||
if (!account_has_statement_period($account)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$startOffset = (int)$account['statement_start_month_offset'];
|
||||
$startDay = (int)$account['statement_start_day'];
|
||||
$endOffset = (int)$account['statement_end_month_offset'];
|
||||
$endDay = (int)$account['statement_end_day'];
|
||||
|
||||
$monthText = function (int $offset): string {
|
||||
if ($offset === -2) return '전전월';
|
||||
if ($offset === -1) return '전월';
|
||||
if ($offset === 0) return '당월';
|
||||
if ($offset === 1) return '익월';
|
||||
if ($offset === 2) return '익익월';
|
||||
return $offset . '개월';
|
||||
};
|
||||
|
||||
return $monthText($startOffset) . ' ' . $startDay . '일 ~ ' .
|
||||
$monthText($endOffset) . ' ' . $endDay . '일 사용분';
|
||||
}
|
||||
|
||||
function get_card_billing_label(array $account): string
|
||||
{
|
||||
if (($account['account_type'] ?? '') !== 'card') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
$cardKind = normalize_card_kind($account);
|
||||
|
||||
if ($cardKind === 'check') {
|
||||
return '체크카드 · 즉시출금';
|
||||
}
|
||||
|
||||
if ($cardKind === 'credit') {
|
||||
$paymentDay = (int)($account['payment_day'] ?? 0);
|
||||
|
||||
if (!empty($account['billing_cycle_memo'])) {
|
||||
return (string)$account['billing_cycle_memo'];
|
||||
}
|
||||
|
||||
$periodLabel = get_card_statement_period_label($account);
|
||||
if ($periodLabel !== null && $paymentDay > 0) {
|
||||
return '신용카드 · ' . $periodLabel . ' / 납부일 ' . $paymentDay . '일';
|
||||
}
|
||||
|
||||
if ($periodLabel !== null) {
|
||||
return '신용카드 · ' . $periodLabel;
|
||||
}
|
||||
|
||||
$billingDay = (int)($account['billing_day'] ?? 0);
|
||||
if ($billingDay > 0 && $paymentDay > 0) {
|
||||
return '신용카드 · 결제기준일 ' . $billingDay . '일 / 납부일 ' . $paymentDay . '일';
|
||||
}
|
||||
|
||||
return '신용카드';
|
||||
}
|
||||
|
||||
return '카드';
|
||||
}
|
||||
Reference in New Issue
Block a user