274 lines
7.5 KiB
PHP
274 lines
7.5 KiB
PHP
<?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 '카드';
|
|
} |