Files
sam-kd/account/list_daily.php
hskwon aca1767eb9 초기 커밋: 5130 레거시 시스템
- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경
- DB 연결 하드코딩 → .env 기반으로 변경
- MySQL strict mode DATE 오류 수정
2025-12-10 20:14:31 +09:00

691 lines
31 KiB
PHP

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
if (!isset($_SESSION["level"]) || $_SESSION["level"] > 5) {
sleep(1);
header("Location:" . $WebSite . "login/login_form.php");
exit;
}
// 에러 표시 설정
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include $_SERVER['DOCUMENT_ROOT'] . '/load_header.php';
$title_message = '일일 일보';
?>
<link href="css/style.css" rel="stylesheet">
<title> <?=$title_message?> </title>
<style>
/* 테이블에 테두리 추가 */
.detail-table th, .detail-table td {
border: 1px solid black;
border-collapse: collapse;
}
/* 테이블 셀 패딩 조정 */
.detail-table th, .detail-table td {
padding: 4px;
text-align: center;
font-size: 0.9em;
}
</style>
</head>
<body>
<?php
// 세무사아이디면 다른 메뉴 연결
if($_SESSION["userid"] == '0266771300') {
include $_SERVER['DOCUMENT_ROOT'] . '/myheader_accountant.php';
}
else {
include $_SERVER['DOCUMENT_ROOT'] . '/myheader.php';
}
$fromdate = isset($_REQUEST['fromdate']) ? $_REQUEST['fromdate'] : '';
$todate = isset($_REQUEST['todate']) ? $_REQUEST['todate'] : '';
// 날짜 기본값 설정: 당월 1일 ~ 오늘
if (empty($fromdate) || empty($todate)) {
$fromdate = date("2025-06-01");
$todate = date("Y-m-d");
}
$tablename = 'account';
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
$pdo = db_connect();
// 1. 계좌 목록 로드
$jsonFile = $_SERVER['DOCUMENT_ROOT'] . "/account/accoutlist.json";
$accounts = file_exists($jsonFile) ? json_decode(file_get_contents($jsonFile), true) : [];
$accountNames = [];
if (is_array($accounts)) {
foreach ($accounts as $acc) {
$accountNames[] = $acc['company'] . ' ' . $acc['number'] . ($acc['memo'] ? ' (' . $acc['memo'] . ')' : '');
}
}
// '전자어음' 계좌를 항상 추가
if (!in_array('전자어음', $accountNames)) {
$accountNames[] = '전자어음';
}
// $accountNames[] = '전자어음';
// echo '<pre>';
// print_r($accountNames);
// echo '</pre>';
// 2. 검색 시작일 이전의 계좌별 기초 잔액 계산 (전일이월의 기초값)
$runningBalances = [];
foreach ($accountNames as $bankbookName) {
$initialBalanceSql = "SELECT
(SUM(CASE WHEN inoutsep = '수입' OR inoutsep = '최초전월이월' THEN REPLACE(amount, ',', '') ELSE 0 END) -
SUM(CASE WHEN inoutsep = '지출' THEN REPLACE(amount, ',', '') ELSE 0 END)) AS balance
FROM $tablename
WHERE (is_deleted = '0' OR is_deleted IS NULL) AND registDate < :fromdate AND bankbook = :bankbook ";
//AND (dueDate = '0000-00-00' OR dueDate IS NULL OR dueDate = '')";
$initialStmh = $pdo->prepare($initialBalanceSql);
$initialStmh->execute([':fromdate' => $fromdate, ':bankbook' => $bankbookName]);
$result = $initialStmh->fetch(PDO::FETCH_ASSOC);
$runningBalances[$bankbookName] = $result['balance'] ?? 0;
}
// 3. 기간 내 모든 현금성 거래내역 한번에 조회 후 날짜별로 그룹화
$allTransactionsSql = "SELECT * FROM $tablename WHERE registDate BETWEEN :fromdate AND :todate AND (is_deleted = 0 OR is_deleted IS NULL)
ORDER BY registDate ASC, num ASC";
// AND content != '외상매출채권(전자어음)'
// AND (dueDate = '0000-00-00' OR dueDate IS NULL OR dueDate = '')
$allTransactionsStmh = $pdo->prepare($allTransactionsSql);
$allTransactionsStmh->execute([':fromdate' => $fromdate, ':todate' => $todate]);
$allTransactions = $allTransactionsStmh->fetchAll(PDO::FETCH_ASSOC);
$dataByDate = [];
foreach ($allTransactions as $tx) {
$dataByDate[$tx['registDate']][] = $tx;
}
// 4. 외상매출채권(전자어음) 잔액 계산 - 배서일자가 없는 경우만 조회
$notesReceivableSql = "
SELECT
-- (1) bill_no
CASE
WHEN i.content_detail LIKE '%(%' AND i.content_detail LIKE '%)%'
THEN SUBSTRING(
i.content_detail,
LOCATE('(', i.content_detail) + 1,
LOCATE(')', i.content_detail)
- LOCATE('(', i.content_detail) - 1
)
ELSE i.content_detail
END AS bill_no,
-- (2) 원발행 정보
i.content_detail AS content_detail,
CASE WHEN CAST(i.endorsementDate AS CHAR) IN ('', '0000-00-00') THEN NULL ELSE i.endorsementDate END AS endorsement_date,
i.registDate AS issue_date,
i.dueDate AS due_date,
i.num AS note_num,
-- (3) 발행금액
i.issued_amount,
-- (4) 배서(지출) 합계 (없으면 0)
COALESCE(e.endorsed_amount, 0) AS endorsed_amount,
-- (5) 최종 잔액
(i.issued_amount - COALESCE(e.endorsed_amount,0)) AS outstanding_balance
FROM
-- 발행만 모은 서브쿼리
(
SELECT
num,
content_detail,
endorsementDate,
registDate,
dueDate,
REPLACE(amount, ',', '') + 0 AS issued_amount
FROM {$tablename}
WHERE inoutsep = '수입'
AND bankbook = '전자어음'
AND (is_deleted = 0 OR is_deleted IS NULL)
) AS i
-- 배서(지출)만 모은 서브쿼리
LEFT JOIN
(
SELECT
parentEBNum AS issuance_num,
SUM(REPLACE(amount, ',', '') + 0) AS endorsed_amount
FROM {$tablename}
WHERE inoutsep = '지출'
AND bankbook = '전자어음'
AND endorsementDate IS NOT NULL
AND CAST(endorsementDate AS CHAR) NOT IN ('', '0000-00-00')
AND (is_deleted = 0 OR is_deleted IS NULL)
GROUP BY parentEBNum
) AS e
ON e.issuance_num = i.num
-- (6) 잔액이 남아 있는 발행만
WHERE (i.issued_amount - COALESCE(e.endorsed_amount,0)) > 0
ORDER BY
i.dueDate ASC
";
$notesStmh = $pdo->query($notesReceivableSql);
$notesReceivable = $notesStmh->fetchAll(PDO::FETCH_ASSOC);
// 5. 제외된 어음 목록 로드
$excludedNotesFile = $_SERVER['DOCUMENT_ROOT'] . "/account/excluded_notes.json";
$excludedNotes = file_exists($excludedNotesFile) ? json_decode(file_get_contents($excludedNotesFile), true) : [];
$excludedNoteIds = array_column($excludedNotes, 'note_num');
// 제외된 어음을 필터링
$notesReceivable = array_filter($notesReceivable, function($note) use ($excludedNoteIds) {
return !in_array($note['note_num'], $excludedNoteIds);
});
// echo '<pre>';
// print_r($notesReceivable);
// echo '</pre>';
// 5. 날짜별로 모든 데이터 계산 후 배열에 저장 (메모리에서 처리)
$reportData = [];
$current = strtotime($fromdate);
$end = strtotime($todate);
while ($current <= $end) {
$currentDateStr = date('Y-m-d', $current);
$dailyTransactions = $dataByDate[$currentDateStr] ?? [];
// 거래가 있는 날만 리포트 데이터 생성
if (!empty($dailyTransactions)) {
$dailySummaries = [];
foreach ($accountNames as $bankbookName) {
$prevBalance = $runningBalances[$bankbookName];
$dailyIncome = 0;
$dailyExpense = 0;
foreach ($dailyTransactions as $tx) {
if ($tx['bankbook'] === $bankbookName) {
$amount = floatval(str_replace(',', '', $tx['amount']));
if ($tx['inoutsep'] === '지출') {
$dailyExpense += $amount;
} else {
$dailyIncome += $amount;
}
}
}
$endOfDayBalance = $prevBalance + $dailyIncome - $dailyExpense;
$dailySummaries[$bankbookName] = [
'prev' => $prevBalance,
'income' => $dailyIncome,
'expense' => $dailyExpense,
'balance' => $endOfDayBalance
];
}
$reportData[$currentDateStr] = [
'summaries' => $dailySummaries,
'transactions' => $dailyTransactions
];
}
// 전일이월액 업데이트는 거래가 없는 날도 매일 수행해야 함
foreach ($accountNames as $bankbookName) {
$dailyIncome = 0; $dailyExpense = 0;
$currentDayTx = $dataByDate[$currentDateStr] ?? [];
foreach ($currentDayTx as $tx) {
if ($tx['bankbook'] === $bankbookName) {
$amount = floatval(str_replace(',', '', $tx['amount']));
if ($tx['inoutsep'] === '지출') $dailyExpense += $amount; else $dailyIncome += $amount;
}
}
$runningBalances[$bankbookName] += ($dailyIncome - $dailyExpense);
}
$current = strtotime('+1 day', $current);
}
// 날짜 역순으로 출력하기 위해 배열 뒤집기
$reportData = array_reverse($reportData, true);
// echo '<pre>';
// print_r($reportData);
// echo '</pre>';
?>
<form id="board_form" name="board_form" method="post">
<div class="container">
<div class="card justify-content-center text-center mt-5">
<div class="card-header">
<span class="text-center fs-5"> <?=$title_message?>
<button type="button" class="btn btn-dark btn-sm me-1" onclick='location.reload()'><i class="bi bi-arrow-clockwise"></i></button>
</span>
</div>
<div class="card-body">
<div class="d-flex justify-content-center align-items-center mt-2">
<input type="date" id="fromdate" name="fromdate" class="form-control" style="width:130px;" value="<?=$fromdate?>">
<span class="mx-2">~</span>
<input type="date" id="todate" name="todate" class="form-control" style="width:130px;" value="<?=$todate?>">
<button class="btn btn-outline-dark btn-sm ms-2" type="submit"><i class="bi bi-search"></i></button>
</div>
</div>
<div class="mt-2 justify-content-center">
<h4 class="text-center alert alert-warning p-2" >어음 및 외상매출채권 현황</h4>
<div class="d-flex justify-content-center mt-2">
<table class="table table-hover table-bordered table-sm w-75">
<thead class="table-warning">
<tr>
<th style="width:45%;">내용</th>
<th style="width:15%;">현재잔액</th>
<th style="width:15%;">최초발행일</th>
<th style="width:15%;">만기일자</th>
<th style="width:10%;">제외</th>
</tr>
</thead>
<tbody>
<?php
$totalOutstandingBalance = 0; // 합계 변수 초기화
if (empty($notesReceivable)): ?>
<tr><td colspan="5">해당 내역이 없습니다.</td></tr>
<?php else: ?>
<?php foreach ($notesReceivable as $note):
$totalOutstandingBalance += $note['outstanding_balance']; // 합계 계산
?>
<tr>
<td class="text-start"><?= htmlspecialchars($note['content_detail']) ?></td>
<td class="text-end fw-bold"><?= number_format($note['outstanding_balance']) ?></td>
<td><?= htmlspecialchars($note['issue_date']) ?></td>
<td class="text-danger fw-bold"><?= htmlspecialchars($note['due_date']) ?></td>
<td class="text-center">
<button type="button" class="btn btn-outline-danger btn-sm"
onclick="excludeNote(<?= $note['note_num'] ?>, '<?= htmlspecialchars($note['content_detail'], ENT_QUOTES) ?>')"
title="이 어음을 목록에서 제외">
<i class="bi bi-x-circle"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
<tfoot class="table-warning">
<tr class="fw-bold">
<td class="text-center">합계</td>
<td class="text-end text-primary"><?= number_format($totalOutstandingBalance) ?></td>
<td></td>
<td></td>
<td class="text-center">
<button type="button" class="btn btn-outline-info btn-sm"
onclick="showExcludedNotes()" title="제외된 어음 목록 보기">
<i class="bi bi-list-ul"></i>
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<?php if (empty($reportData)): ?>
<div class="alert alert-warning text-center mt-4" role="alert">
해당 기간에 거래 내역이 없습니다.
</div>
<?php else: ?>
<?php foreach ($reportData as $date => $dayData): ?>
<div class="card mt-4 mb-4">
<div class="card-body">
<div class="row d-flex justify-content-center m-1 mb-2">
<?php
$formatter = new IntlDateFormatter('ko_KR', IntlDateFormatter::FULL, IntlDateFormatter::NONE, null, null, 'Y년 M월 d일 EEEE');
$formattedDate = $formatter->format(strtotime($date));
?>
<div class="alert alert-primary fs-4 text-center">
일자: <?=$formattedDate?> &nbsp; / &nbsp; 작성자 : 정미영
</div>
</div>
<div class="table-responsive d-flex justify-content-center mainTable">
<table class="table table-hover table-bordered table-sm w-75">
<thead class="table-secondary">
<tr>
<th class="text-center" style="width:40%;">구분</th>
<th class="text-center" style="width:15%;">전일이월</th>
<th class="text-center" style="width:15%;">수입</th>
<th class="text-center" style="width:15%;">지출</th>
<th class="text-center" style="width:15%;">잔액</th>
</tr>
</thead>
<tbody>
<?php
$grandTotal_prev = 0; $grandTotal_income = 0; $grandTotal_expense = 0; $grandTotal_balance = 0;
$usdTotal_prev = 0; $usdTotal_income = 0; $usdTotal_expense = 0; $usdTotal_balance = 0;
foreach (array_filter($accountNames, function($name) { return $name !== '전자어음'; }) as $bankbookName):
$summary = $dayData['summaries'][$bankbookName];
$isUSD = strpos($bankbookName, 'USD') !== false;
if ($isUSD) {
$usdTotal_prev += $summary['prev'];
$usdTotal_income += $summary['income'];
$usdTotal_expense += $summary['expense'];
$usdTotal_balance += $summary['balance'];
} else {
$grandTotal_prev += $summary['prev'];
$grandTotal_income += $summary['income'];
$grandTotal_expense += $summary['expense'];
$grandTotal_balance += $summary['balance'];
}
?>
<tr>
<td class="text-start"><?= htmlspecialchars($bankbookName) ?></td>
<td class="text-end"><?= $isUSD ? '$' : '' ?><?= number_format($summary['prev']) ?></td>
<td class="text-end"><?= $isUSD ? '$' : '' ?><?= number_format($summary['income']) ?></td>
<td class="text-end"><?= $isUSD ? '$' : '' ?><?= number_format($summary['expense']) ?></td>
<td class="text-end fw-bold"><?= $isUSD ? '$' : '' ?><?= number_format($summary['balance']) ?></td>
</tr>
<?php endforeach; ?>
<?php if ($usdTotal_prev != 0 || $usdTotal_income != 0 || $usdTotal_expense != 0 || $usdTotal_balance != 0): ?>
<tr class="table-group-divider">
<td class="text-center fw-bold">외국환(USD) 합계</td>
<td class="text-end fw-bold">$<?= number_format($usdTotal_prev) ?></td>
<td class="text-end fw-bold">$<?= number_format($usdTotal_income) ?></td>
<td class="text-end fw-bold">$<?= number_format($usdTotal_expense) ?></td>
<td class="text-end fw-bold">$<?= number_format($usdTotal_balance) ?></td>
</tr>
<?php endif; ?>
<tr class="table-group-divider">
<td class="text-center fw-bold">현금성 자산 합계</td>
<td class="text-end fw-bold"><?= number_format($grandTotal_prev) ?></td>
<td class="text-end fw-bold"><?= number_format($grandTotal_income) ?></td>
<td class="text-end fw-bold"><?= number_format($grandTotal_expense) ?></td>
<td class="text-end fw-bold"><?= number_format($grandTotal_balance) ?></td>
</tr>
</tbody>
</table>
</div>
<div class="mt-4">
<h4 class="text-center alert alert-success p-2"><예금 입출금 내역></h4>
<?php
$transactionsByAccount = [];
foreach ($dayData['transactions'] as $row) {
$isNormalAccount = true;
$isElectronicBill = ($row['bankbook'] === '전자어음' || strpos($row['content'], '전자어음') !== false || strpos($row['content_detail'], '전자어음') !== false);
// 기존 계좌별 분류 + 전자어음 관련 내역도 별도 그룹으로 분류
if ($isElectronicBill) {
$transactionsByAccount['전자어음'][] = $row;
} else {
$transactionsByAccount[$row['bankbook']][] = $row;
}
}
foreach ($transactionsByAccount as $bankbookName => $transactions):
$isForeignExchange = strpos($bankbookName, '외국환') !== false;
?>
<h5 class="mt-3 text-primary-emphasis"><?= htmlspecialchars($bankbookName) ?></h5>
<div class="table-responsive d-flex justify-content-center">
<table class="table table-bordered detail-table w-75">
<thead class="table-group-divider">
<tr>
<th style="width:35%;">입금내역</th>
<th style="width:15%;">금액</th>
<th style="width:35%;">출금내역</th>
<th style="width:15%;">금액</th>
</tr>
</thead>
<tbody>
<?php
$incomeRows = array_filter($transactions, function($tx) { return $tx['inoutsep'] !== '지출'; });
$expenseRows = array_filter($transactions, function($tx) { return $tx['inoutsep'] === '지출'; });
$maxRows = max(count($incomeRows), count($expenseRows));
$dailyIncomeTotal = 0; $dailyExpenseTotal = 0;
$incomeRows = array_values($incomeRows); $expenseRows = array_values($expenseRows);
for ($i = 0; $i < $maxRows; $i++):
?>
<tr>
<?php if (isset($incomeRows[$i])):
$item = $incomeRows[$i];
$amount = floatval(str_replace(',', '', $item['amount']));
$dailyIncomeTotal += $amount;
?>
<td class="text-start"><?= htmlspecialchars($item['content_detail'] ? $item['content_detail'] : "") ?></td>
<td class="text-end"><?= $isForeignExchange ? '$' : '' ?><?= number_format($amount) ?></td>
<?php else: ?>
<td></td><td></td>
<?php endif; ?>
<?php if (isset($expenseRows[$i])):
$item = $expenseRows[$i];
$amount = floatval(str_replace(',', '', $item['amount']));
$dailyExpenseTotal += $amount;
?>
<td class="text-start"><?= htmlspecialchars( $item['content_detail'] ? " " . $item['content_detail'] : "") ?></td>
<td class="text-end"><?= $isForeignExchange ? '$' : '' ?><?= number_format($amount) ?></td>
<?php else: ?>
<td></td><td></td>
<?php endif; ?>
</tr>
<?php endfor; ?>
</tbody>
<tfoot class="table-group-divider">
<tr class="fw-bold">
<td>입금 합계</td>
<td class="text-end text-primary"><?= $isForeignExchange ? '$' : '' ?><?= number_format($dailyIncomeTotal) ?></td>
<td>출금 합계</td>
<td class="text-end text-danger"><?= $isForeignExchange ? '$' : '' ?><?= number_format($dailyExpenseTotal) ?></td>
</tr>
</tfoot>
</table>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</form>
<script>
document.addEventListener('DOMContentLoaded', function() {
var loader = document.getElementById('loadingOverlay');
if(loader) {
loader.style.display = 'none';
}
if (typeof saveLogData === 'function') {
saveLogData('회계 일일 일보');
}
});
// 어음 제외 기능
function excludeNote(noteNum, contentDetail) {
if (confirm('이 어음을 목록에서 제외하시겠습니까?\n\n' + contentDetail)) {
// AJAX로 제외 요청
$.ajax({
url: 'exclude_note.php',
type: 'POST',
data: {
action: 'exclude',
note_num: noteNum,
content_detail: contentDetail
},
dataType: 'json',
success: function(response) {
if (response.success) {
Toastify({
text: "어음이 제외되었습니다.",
duration: 2000,
close: true,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #ff6b6b, #ee5a24)"
}
}).showToast();
// 페이지 새로고침
setTimeout(function() {
location.reload();
}, 1000);
} else {
alert('제외 처리 중 오류가 발생했습니다: ' + response.message);
}
},
error: function(xhr, status, error) {
console.error('제외 요청 실패:', error);
alert('제외 처리 중 오류가 발생했습니다.');
}
});
}
}
// 제외된 어음 목록 보기
function showExcludedNotes() {
$.ajax({
url: 'exclude_note.php',
type: 'POST',
data: {
action: 'get_excluded'
},
dataType: 'json',
success: function(response) {
if (response.success) {
showExcludedModal(response.data);
} else {
alert('제외된 어음 목록을 불러오는 중 오류가 발생했습니다.');
}
},
error: function(xhr, status, error) {
console.error('제외된 어음 목록 요청 실패:', error);
alert('제외된 어음 목록을 불러오는 중 오류가 발생했습니다.');
}
});
}
// 제외된 어음 복원
function restoreNote(noteNum) {
if (confirm('이 어음을 목록에 복원하시겠습니까?')) {
$.ajax({
url: 'exclude_note.php',
type: 'POST',
data: {
action: 'restore',
note_num: noteNum
},
dataType: 'json',
success: function(response) {
if (response.success) {
Toastify({
text: "어음이 복원되었습니다.",
duration: 2000,
close: true,
gravity: "top",
position: "center",
style: {
background: "linear-gradient(to right, #00b09b, #96c93d)"
}
}).showToast();
// 모달 닫기 및 페이지 새로고침
$('#excludedNotesModal').modal('hide');
setTimeout(function() {
location.reload();
}, 1000);
} else {
alert('복원 처리 중 오류가 발생했습니다: ' + response.message);
}
},
error: function(xhr, status, error) {
console.error('복원 요청 실패:', error);
alert('복원 처리 중 오류가 발생했습니다.');
}
});
}
}
// 제외된 어음 모달 표시
function showExcludedModal(excludedNotes) {
let modalHtml = `
<div class="modal fade" id="excludedNotesModal" tabindex="-1" aria-labelledby="excludedNotesModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="excludedNotesModalLabel">제외된 어음 목록</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 500px; overflow-y: auto;">
`;
if (excludedNotes.length === 0) {
modalHtml += '<div class="alert alert-info text-center">제외된 어음이 없습니다.</div>';
} else {
modalHtml += `
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead class="table-secondary">
<tr>
<th style="width:50%;">내용</th>
<th style="width:20%;">제외일자</th>
<th style="width:20%;">제외자</th>
<th style="width:10%;">복원</th>
</tr>
</thead>
<tbody>
`;
excludedNotes.forEach(function(note) {
modalHtml += `
<tr>
<td class="text-start">${note.content_detail}</td>
<td>${note.excluded_date}</td>
<td>${note.excluded_by || '-'}</td>
<td class="text-center">
<button type="button" class="btn btn-outline-success btn-sm"
onclick="restoreNote(${note.note_num})" title="복원">
<i class="bi bi-arrow-clockwise"></i>
</button>
</td>
</tr>
`;
});
modalHtml += `
</tbody>
</table>
</div>
`;
}
modalHtml += `
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
`;
// 기존 모달이 있으면 제거
$('#excludedNotesModal').remove();
// 새 모달 추가
$('body').append(modalHtml);
// 모달 표시
$('#excludedNotesModal').modal('show');
}
</script>
</body>
</html>