금융 거래 기록 흐름 공통화
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
require_once __DIR__ . '/account_service.php';
|
||||
require_once __DIR__ . '/card_billing_service.php';
|
||||
|
||||
function build_transaction_fingerprint(
|
||||
int $userId,
|
||||
int $accountId,
|
||||
?int $relatedAccountId,
|
||||
int $categoryId,
|
||||
string $transactionType,
|
||||
float $amount,
|
||||
string $transactionDate,
|
||||
?string $merchantName,
|
||||
?string $description,
|
||||
?int $sourceLoanId = null
|
||||
): string {
|
||||
$parts = [
|
||||
$userId,
|
||||
$accountId,
|
||||
$relatedAccountId ?? 0,
|
||||
$categoryId,
|
||||
$transactionType,
|
||||
number_format($amount, 2, '.', ''),
|
||||
$transactionDate,
|
||||
trim((string)$merchantName),
|
||||
trim((string)$description),
|
||||
];
|
||||
|
||||
if ($sourceLoanId !== null) {
|
||||
$parts[] = $sourceLoanId;
|
||||
}
|
||||
|
||||
return hash('sha256', implode('|', $parts));
|
||||
}
|
||||
|
||||
function get_account_for_transaction(int $userId, int $accountId): ?array
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT *
|
||||
FROM accounts
|
||||
WHERE id = ?
|
||||
AND user_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$accountId, $userId]);
|
||||
|
||||
$row = $stmt->fetch();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
function get_transaction_billing_year_month(int $userId, int $accountId, string $transactionDate): ?string
|
||||
{
|
||||
$account = get_account_for_transaction($userId, $accountId);
|
||||
return $account ? get_card_billing_year_month($account, $transactionDate) : null;
|
||||
}
|
||||
|
||||
function transaction_category_type(string $transactionType): string
|
||||
{
|
||||
if ($transactionType === 'income') {
|
||||
return 'income';
|
||||
}
|
||||
|
||||
if ($transactionType === 'expense') {
|
||||
return 'expense';
|
||||
}
|
||||
|
||||
return 'transfer';
|
||||
}
|
||||
|
||||
function parse_money_input($value): float
|
||||
{
|
||||
return (float)str_replace(',', '', trim((string)$value));
|
||||
}
|
||||
|
||||
function build_transaction_payload_from_input(array $input, int $userId): array
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$transactionType = (string)($input['transaction_type'] ?? 'expense');
|
||||
$accountId = (int)($input['account_id'] ?? 0);
|
||||
$categoryId = (int)($input['category_id'] ?? 0);
|
||||
$amount = parse_money_input($input['amount'] ?? 0);
|
||||
$transactionDate = (string)($input['transaction_date'] ?? date('Y-m-d'));
|
||||
$merchantName = trim((string)($input['merchant_name'] ?? ''));
|
||||
$description = trim((string)($input['description'] ?? ''));
|
||||
$relatedAccountId = !empty($input['related_account_id']) ? (int)$input['related_account_id'] : null;
|
||||
$isInstallment = (int)($input['is_installment'] ?? 0);
|
||||
$installmentMonths = trim((string)($input['installment_months'] ?? '')) !== ''
|
||||
? (int)$input['installment_months']
|
||||
: null;
|
||||
$installmentInterestRate = trim((string)($input['installment_interest_rate'] ?? '0')) !== ''
|
||||
? (float)$input['installment_interest_rate']
|
||||
: 0.0;
|
||||
$installmentInterestTotal = trim((string)($input['installment_interest_total'] ?? '')) !== ''
|
||||
? parse_money_input($input['installment_interest_total'])
|
||||
: null;
|
||||
$installmentTotalBilled = trim((string)($input['installment_total_billed'] ?? '')) !== ''
|
||||
? parse_money_input($input['installment_total_billed'])
|
||||
: null;
|
||||
|
||||
if ($amount <= 0) {
|
||||
throw new RuntimeException('금액은 0보다 커야 합니다.');
|
||||
}
|
||||
|
||||
if (!in_array($transactionType, ['income', 'expense', 'transfer', 'card_payment'], true)) {
|
||||
throw new RuntimeException('거래 유형이 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
if ($accountId <= 0) {
|
||||
throw new RuntimeException('주 계좌/카드를 선택하세요.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM accounts WHERE id = ? AND user_id = ? AND is_active = 1 LIMIT 1");
|
||||
$stmt->execute([$accountId, $userId]);
|
||||
if (!$stmt->fetch()) {
|
||||
throw new RuntimeException('선택한 주 계좌/카드를 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
if ($categoryId <= 0) {
|
||||
throw new RuntimeException('카테고리를 선택하세요.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT category_type
|
||||
FROM categories
|
||||
WHERE id = ?
|
||||
AND user_id = ?
|
||||
AND is_active = 1
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$categoryId, $userId]);
|
||||
$category = $stmt->fetch();
|
||||
if (!$category) {
|
||||
throw new RuntimeException('선택한 카테고리를 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
if ((string)$category['category_type'] !== transaction_category_type($transactionType)) {
|
||||
throw new RuntimeException('거래 유형과 카테고리 유형이 일치하지 않습니다.');
|
||||
}
|
||||
|
||||
if (in_array($transactionType, ['transfer', 'card_payment'], true) && !$relatedAccountId) {
|
||||
throw new RuntimeException('관련 계좌를 선택해야 합니다.');
|
||||
}
|
||||
|
||||
if ($relatedAccountId) {
|
||||
if ($relatedAccountId === $accountId) {
|
||||
throw new RuntimeException('주 계좌와 관련 계좌는 같을 수 없습니다.');
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("SELECT id FROM accounts WHERE id = ? AND user_id = ? AND is_active = 1 LIMIT 1");
|
||||
$stmt->execute([$relatedAccountId, $userId]);
|
||||
if (!$stmt->fetch()) {
|
||||
throw new RuntimeException('선택한 관련 계좌를 사용할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($transactionType !== 'expense') {
|
||||
$isInstallment = 0;
|
||||
$installmentMonths = null;
|
||||
$installmentInterestRate = 0.0;
|
||||
$installmentInterestTotal = null;
|
||||
$installmentTotalBilled = null;
|
||||
}
|
||||
|
||||
return [
|
||||
'user_id' => $userId,
|
||||
'account_id' => $accountId,
|
||||
'category_id' => $categoryId,
|
||||
'transaction_type' => $transactionType,
|
||||
'amount' => $amount,
|
||||
'transaction_date' => $transactionDate,
|
||||
'merchant_name' => $merchantName !== '' ? $merchantName : null,
|
||||
'description' => $description !== '' ? $description : null,
|
||||
'related_account_id' => $relatedAccountId,
|
||||
'is_installment' => $isInstallment,
|
||||
'installment_months' => $installmentMonths,
|
||||
'installment_interest_rate' => $installmentInterestRate,
|
||||
'installment_interest_total' => $installmentInterestTotal,
|
||||
'installment_total_billed' => $installmentTotalBilled,
|
||||
];
|
||||
}
|
||||
|
||||
function insert_transaction_record(array $data, bool $skipIfDuplicate = true): int
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$sourceLoanId = !empty($data['source_loan_id']) ? (int)$data['source_loan_id'] : null;
|
||||
$fingerprint = build_transaction_fingerprint(
|
||||
(int)$data['user_id'],
|
||||
(int)$data['account_id'],
|
||||
!empty($data['related_account_id']) ? (int)$data['related_account_id'] : null,
|
||||
(int)$data['category_id'],
|
||||
(string)$data['transaction_type'],
|
||||
(float)$data['amount'],
|
||||
(string)$data['transaction_date'],
|
||||
$data['merchant_name'] ?? null,
|
||||
$data['description'] ?? null,
|
||||
$sourceLoanId
|
||||
);
|
||||
|
||||
if ($skipIfDuplicate) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id
|
||||
FROM transactions
|
||||
WHERE user_id = ?
|
||||
AND fingerprint = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$data['user_id'], $fingerprint]);
|
||||
$exists = $stmt->fetch();
|
||||
|
||||
if ($exists) {
|
||||
return (int)$exists['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$billingYearMonth = get_transaction_billing_year_month(
|
||||
(int)$data['user_id'],
|
||||
(int)$data['account_id'],
|
||||
(string)$data['transaction_date']
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO transactions
|
||||
(
|
||||
user_id,
|
||||
account_id,
|
||||
category_id,
|
||||
transaction_type,
|
||||
amount,
|
||||
is_installment,
|
||||
installment_months,
|
||||
installment_interest_rate,
|
||||
installment_interest_total,
|
||||
installment_total_billed,
|
||||
installment_prepay_amount,
|
||||
transaction_date,
|
||||
billing_year_month,
|
||||
merchant_name,
|
||||
description,
|
||||
related_account_id,
|
||||
source_loan_id,
|
||||
fingerprint
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$data['user_id'],
|
||||
$data['account_id'],
|
||||
$data['category_id'],
|
||||
$data['transaction_type'],
|
||||
$data['amount'],
|
||||
!empty($data['is_installment']) ? 1 : 0,
|
||||
$data['installment_months'] ?? null,
|
||||
$data['installment_interest_rate'] ?? 0,
|
||||
$data['installment_interest_total'] ?? 0,
|
||||
$data['installment_total_billed'] ?? null,
|
||||
$data['installment_prepay_amount'] ?? 0,
|
||||
$data['transaction_date'],
|
||||
$billingYearMonth,
|
||||
$data['merchant_name'] ?? null,
|
||||
$data['description'] ?? null,
|
||||
$data['related_account_id'] ?? null,
|
||||
$sourceLoanId,
|
||||
$fingerprint,
|
||||
]);
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
Reference in New Issue
Block a user