- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
1986 lines
70 KiB
PHP
1986 lines
70 KiB
PHP
<?php
|
|
/**
|
|
* 월별 수입/지출 예상 내역서
|
|
*
|
|
* 이 파일은 경동기업 월별 수입/지출 예상 내역서를 생성하고 표시합니다.
|
|
* 사용자 세션을 확인하고 권한에 따라 페이지 접근을 제어합니다.
|
|
* 데이터베이스에서 데이터를 조회하고 계산하여 HTML 테이블 형태로 출력합니다.
|
|
*/
|
|
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);
|
|
|
|
// HTML 헤더 로드
|
|
include $_SERVER['DOCUMENT_ROOT'] . '/load_header.php';
|
|
$title_message = '월별 수입/지출 예상내역서';
|
|
|
|
// 데이터베이스 연결 설정 파일 로드
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
|
|
|
// DB명 하드코딩 (mydb.php에서 db_name은 chandj)
|
|
if(empty($DB)) {
|
|
$DB = 'chandj';
|
|
}
|
|
|
|
/**
|
|
* 데이터베이스 쿼리를 실행하고 결과를 반환합니다.
|
|
*
|
|
* @param PDO $pdo PDO 객체
|
|
* @param string $sql 실행할 SQL 쿼리
|
|
* @param array $params 쿼리에 바인딩할 파라미터 배열 (선택 사항)
|
|
* @return array|false 쿼리 결과 (연관 배열) 또는 실패 시 false
|
|
* @throws PDOException 쿼리 실행 중 오류가 발생한 경우
|
|
*/
|
|
function executeQuery(PDO $pdo, string $sql, array $params = [])
|
|
{
|
|
try {
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
} catch (PDOException $e) {
|
|
error_log("Query execution failed: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
?>
|
|
|
|
<link href="css/style.css" rel="stylesheet">
|
|
<title><?= $title_message ?></title>
|
|
<style>
|
|
/* 테이블에 테두리 추가 */
|
|
#detailTable,
|
|
#detailTable th,
|
|
#detailTable td {
|
|
border: 1px solid black;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
/* 테이블 셀 패딩 조정 */
|
|
#detailTable th,
|
|
#detailTable td {
|
|
padding: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.custom-modal-body {
|
|
max-height: 600px;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<?php
|
|
// 사용자 정의 헤더 로드
|
|
|
|
if($user_id === '0266771300' ) {
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . '/myheader_accountant1.php'); // 경리
|
|
} else {
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . '/myheader1.php');
|
|
}
|
|
|
|
// 동적 대용랴 모달창 가져오기 js, css 파일 가져옴
|
|
require_once($_SERVER['DOCUMENT_ROOT'].'/modal/bulkModal.php');
|
|
// 미수금 데이터 가져오기
|
|
require_once($_SERVER['DOCUMENT_ROOT'] . "/account_juil/fetch_balance.php");
|
|
|
|
// DB명 하드코딩 (mydb.php에서 db_name은 chandj)
|
|
$DB = 'chandj';
|
|
$tablename = 'account_plan_juil';
|
|
$tablename_in = 'account_plan_in_juil';
|
|
|
|
// 파라미터에서 검색어, 연월 가져오기, 없으면 기본값 설정
|
|
$searchKeyword = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
|
$selectedYearMonth = isset($_REQUEST['yearMonth']) ? $_REQUEST['yearMonth'] : date("Ym");
|
|
|
|
// 기준 연월에 따른 fromdate, todate 설정 (검색 기간)
|
|
$startDate = $selectedYearMonth . '01'; // Ym01
|
|
$endDate = date("Y-m-t", strtotime($startDate)); // Y-m-마지막날
|
|
$formattedStartDate = date("Y-m-d", strtotime($startDate)); // Y-m-d
|
|
$formattedEndDate = date("Y-m-d", strtotime($endDate)); // Y-m-d
|
|
|
|
// 거래처별 잔액 계산 (output/output_extra 기반)
|
|
$balances = fetch_balances($DB, $startDate, $endDate);
|
|
|
|
// 거래처 목록
|
|
$allVendors = array_keys($balances);
|
|
|
|
// 미수금 계산 및 거래처명 조회
|
|
$receivables = [];
|
|
$vendorNames = [];
|
|
foreach ($allVendors as $secondordnum) {
|
|
$balance = isset($balances[$secondordnum]) ? $balances[$secondordnum] : 0;
|
|
$receivableAmount = ($balance > 0) ? $balance : 0;
|
|
$receivables[$secondordnum] = $receivableAmount;
|
|
// 거래처 이름 가져오기
|
|
$vendorNameSql = "SELECT vendor_name FROM pb_juil WHERE secondordnum = :secondordnum AND (is_deleted IS NULL OR is_deleted = 0)";
|
|
$vendorNameParams = [':secondordnum' => $secondordnum];
|
|
$vendorNameResult = executeQuery($pdo, $vendorNameSql, $vendorNameParams);
|
|
$vendorNames[$secondordnum] = $vendorNameResult[0]['vendor_name'] ?? '';
|
|
}
|
|
|
|
// 수입 내역 (계산서 발행 등은 기존 monthly_sales 테이블 사용, 필요시 output 기반으로 확장)
|
|
$incomeData = [];
|
|
foreach ($receivables as $secondordnum => $receivableAmount) {
|
|
if ($receivableAmount > 0) {
|
|
$incomeData[] = [
|
|
'customer_name' => $vendorNames[$secondordnum],
|
|
'receivableAmount' => $receivableAmount,
|
|
'totalAmount' => $receivableAmount,
|
|
'secondordnum' => $secondordnum
|
|
];
|
|
}
|
|
}
|
|
|
|
// 현재 잔액 계산서
|
|
$currentDate = date("Y-m-d"); // 현재 날짜
|
|
|
|
// fromdate 또는 todate가 빈 문자열이거나 null인 경우
|
|
if (empty($formattedStartDate) || empty($formattedEndDate)) {
|
|
// 현재 월의 1일을 fromdate로 설정
|
|
$formattedStartDate = date("Y-m-01");
|
|
// 전달의 29일 구하기
|
|
// $formattedStartDate = date("Y-m-29", strtotime("-1 month", strtotime($formattedStartDate)));
|
|
$formattedEndDate = $currentDate;
|
|
$Transtodate = $formattedEndDate;
|
|
} else {
|
|
$Transtodate = $formattedEndDate;
|
|
}
|
|
|
|
/**
|
|
* 문자열이 null이 아니고 빈 문자열이 아닌지 확인합니다.
|
|
*
|
|
* @param string|null $str 확인할 문자열
|
|
* @return bool 문자열이 null이 아니고 비어 있지 않으면 true, 그렇지 않으면 false
|
|
*/
|
|
function checkNull(?string $str): bool
|
|
{
|
|
return $str !== null && trim($str) !== '';
|
|
}
|
|
|
|
// account_juil 테이블 쿼리 조건 및 파라미터 설정
|
|
$accountQueryConditions = [];
|
|
$accountQueryParams = [];
|
|
|
|
if (checkNull($searchKeyword)) {
|
|
$accountQueryConditions[] = "searchtag LIKE :searchKeyword";
|
|
$accountQueryParams[':searchKeyword'] = "%$searchKeyword%";
|
|
}
|
|
|
|
$accountQueryConditions[] = "registDate BETWEEN :formattedStartDate AND :formattedEndDate";
|
|
$accountQueryParams[':formattedStartDate'] = $formattedStartDate;
|
|
$accountQueryParams[':formattedEndDate'] = $formattedEndDate;
|
|
|
|
$accountQueryConditions[] = "(is_deleted = 0 OR is_deleted IS NULL)";
|
|
|
|
// 수입/지출 구분
|
|
$inoutType = isset($_REQUEST['inoutsep_select']) ? $_REQUEST['inoutsep_select'] : '';
|
|
if (checkNull($inoutType)) {
|
|
$accountQueryConditions[] = "inoutsep = :inoutType";
|
|
$accountQueryParams[':inoutType'] = $inoutType;
|
|
}
|
|
|
|
// 내용
|
|
$contentType = isset($_REQUEST['content_select']) ? $_REQUEST['content_select'] : '';
|
|
if (checkNull($contentType)) {
|
|
$accountQueryConditions[] = "content = :contentType";
|
|
$accountQueryParams[':contentType'] = $contentType;
|
|
}
|
|
|
|
// account_juil 테이블에서 데이터 조회
|
|
$accountTable = $DB . '.account_juil';
|
|
$accountOrder = " ORDER BY registDate ASC, num ASC ";
|
|
$accountSql = "SELECT * FROM {$accountTable} WHERE " . implode(' AND ', $accountQueryConditions) . $accountOrder;
|
|
|
|
// PDO prepared statement 사용
|
|
$stmt = $pdo->prepare($accountSql);
|
|
$stmt->execute($accountQueryParams);
|
|
$accountData = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 수입, 지출을 기반으로 초기 잔액 계산
|
|
$initialBalanceSql = "
|
|
SELECT
|
|
SUM(CASE WHEN inoutsep = '수입' THEN REPLACE(amount, ',', '') ELSE 0 END) +
|
|
SUM(CASE WHEN inoutsep = '최초전월이월' THEN REPLACE(amount, ',', '') ELSE 0 END) -
|
|
SUM(CASE WHEN inoutsep = '지출' THEN REPLACE(amount, ',', '') ELSE 0 END) AS balance
|
|
FROM {$accountTable}
|
|
WHERE is_deleted = '0' AND registDate < :formattedStartDate
|
|
";
|
|
$initialBalanceParams = [':formattedStartDate' => $formattedStartDate];
|
|
$initialBalanceResult = executeQuery($pdo, $initialBalanceSql, $initialBalanceParams);
|
|
$initialBalance = $initialBalanceResult[0]['balance'];
|
|
|
|
// 총 수입 계산
|
|
$totalIncomeSql = "
|
|
SELECT SUM(REPLACE(amount, ',', '')) AS totalIncome
|
|
FROM {$accountTable}
|
|
WHERE is_deleted = '0' AND (inoutsep = '수입' OR inoutsep = '최초전월이월')
|
|
AND registDate BETWEEN :formattedStartDate AND :formattedEndDate
|
|
";
|
|
$totalIncomeParams = [
|
|
':formattedStartDate' => $formattedStartDate,
|
|
':formattedEndDate' => $formattedEndDate
|
|
];
|
|
$totalIncomeResult = executeQuery($pdo, $totalIncomeSql, $totalIncomeParams);
|
|
$totalIncome = $totalIncomeResult[0]['totalIncome'];
|
|
|
|
// 총 지출 계산
|
|
$totalExpenseSql = "
|
|
SELECT SUM(REPLACE(amount, ',', '')) AS totalExpense
|
|
FROM {$accountTable}
|
|
WHERE is_deleted = '0' AND inoutsep = '지출'
|
|
AND registDate BETWEEN :formattedStartDate AND :formattedEndDate
|
|
";
|
|
$totalExpenseParams = [
|
|
':formattedStartDate' => $formattedStartDate,
|
|
':formattedEndDate' => $formattedEndDate
|
|
];
|
|
$totalExpenseResult = executeQuery($pdo, $totalExpenseSql, $totalExpenseParams);
|
|
$totalExpense = $totalExpenseResult[0]['totalExpense'];
|
|
|
|
// 최종 잔액 계산
|
|
$finalBalance = $initialBalance + $totalIncome - $totalExpense;
|
|
|
|
|
|
|
|
|
|
// Bankbook options
|
|
$bankbookOptions = [];
|
|
$jsonFile = $_SERVER['DOCUMENT_ROOT'] . "/account_juil/accoutlist.json";
|
|
$accounts = [];
|
|
$selectedAccount = null;
|
|
$accountBalances = [];
|
|
|
|
if (file_exists($jsonFile)) {
|
|
$jsonContent = file_get_contents($jsonFile);
|
|
$accounts = json_decode($jsonContent, true);
|
|
if (is_array($accounts) && !empty($accounts)) {
|
|
// 선택된 계좌 또는 기본 계좌(첫 번째) 설정
|
|
$selectedAccountIndex = isset($_REQUEST['selected_account']) ? intval($_REQUEST['selected_account']) : 0;
|
|
$selectedAccount = $accounts[$selectedAccountIndex] ?? $accounts[0];
|
|
|
|
// 각 계좌별 잔액 계산
|
|
foreach ($accounts as $index => $account) {
|
|
$accountDisplay = $account['company'] . ' ' . $account['number'];
|
|
if (!empty($account['memo'])) {
|
|
$accountDisplay .= ' (' . $account['memo'] . ')';
|
|
}
|
|
|
|
// 해당 계좌의 잔액 계산
|
|
$accountBalanceSql = "SELECT
|
|
SUM(CASE WHEN inoutsep = '수입' AND bankbook = ? THEN REPLACE(amount, ',', '') ELSE 0 END) +
|
|
SUM(CASE WHEN inoutsep = '최초전월이월' AND bankbook = ? THEN REPLACE(amount, ',', '') ELSE 0 END) -
|
|
SUM(CASE WHEN inoutsep = '지출' AND bankbook = ? THEN REPLACE(amount, ',', '') ELSE 0 END) AS balance
|
|
FROM $tablename
|
|
WHERE is_deleted = '0'";
|
|
$accountBalanceStmh = $pdo->prepare($accountBalanceSql);
|
|
$accountBalanceStmh->bindValue(1, $accountDisplay, PDO::PARAM_STR);
|
|
$accountBalanceStmh->bindValue(2, $accountDisplay, PDO::PARAM_STR);
|
|
$accountBalanceStmh->bindValue(3, $accountDisplay, PDO::PARAM_STR);
|
|
$accountBalanceStmh->execute();
|
|
$accountBalances[$index] = $accountBalanceStmh->fetch(PDO::FETCH_ASSOC)['balance'] ?? 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 각 계좌별 요약 정보를 담을 배열 초기화
|
|
$accountSummaries = [];
|
|
if (is_array($accounts)) {
|
|
foreach ($accounts as $index => $account) {
|
|
$accountDisplay = $account['company'] . ' ' . $account['number'];
|
|
if (!empty($account['memo'])) {
|
|
$accountDisplay .= ' (' . $account['memo'] . ')';
|
|
}
|
|
|
|
// 1. 계좌별 기초 잔액 (기간 이전)
|
|
$initialSql = "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' AND registDate < :fromdate AND bankbook = :bankbook";
|
|
$initialStmh = $pdo->prepare($initialSql);
|
|
$initialStmh->bindParam(':fromdate', $fromdate);
|
|
$initialStmh->bindParam(':bankbook', $accountDisplay);
|
|
$initialStmh->execute();
|
|
$initialBalanceAccount = $initialStmh->fetch(PDO::FETCH_ASSOC)['balance'] ?? 0;
|
|
|
|
// 2. 기간 내 수입
|
|
$incomeSql = "SELECT SUM(REPLACE(amount, ',', '')) AS totalIncome
|
|
FROM $tablename
|
|
WHERE is_deleted = '0' AND (inoutsep = '수입' OR inoutsep = '최초전월이월')
|
|
AND registDate BETWEEN :fromdate AND :todate AND bankbook = :bankbook";
|
|
$incomeStmh = $pdo->prepare($incomeSql);
|
|
$incomeStmh->bindParam(':fromdate', $fromdate);
|
|
$incomeStmh->bindParam(':todate', $todate);
|
|
$incomeStmh->bindParam(':bankbook', $accountDisplay);
|
|
$incomeStmh->execute();
|
|
$totalIncomeAccount = $incomeStmh->fetch(PDO::FETCH_ASSOC)['totalIncome'] ?? 0;
|
|
|
|
// 3. 기간 내 지출
|
|
$expenseSql = "SELECT SUM(REPLACE(amount, ',', '')) AS totalExpense
|
|
FROM $tablename
|
|
WHERE is_deleted = '0' AND inoutsep = '지출'
|
|
AND registDate BETWEEN :fromdate AND :todate AND bankbook = :bankbook";
|
|
$expenseStmh = $pdo->prepare($expenseSql);
|
|
$expenseStmh->bindParam(':fromdate', $fromdate);
|
|
$expenseStmh->bindParam(':todate', $todate);
|
|
$expenseStmh->bindParam(':bankbook', $accountDisplay);
|
|
$expenseStmh->execute();
|
|
$totalExpenseAccount = $expenseStmh->fetch(PDO::FETCH_ASSOC)['totalExpense'] ?? 0;
|
|
|
|
// 4. 최종 잔액
|
|
$finalBalanceAccount = $initialBalanceAccount + $totalIncomeAccount - $totalExpenseAccount;
|
|
|
|
// 계산된 정보를 배열에 저장
|
|
$accountSummaries[$index] = [
|
|
'name' => $accountDisplay,
|
|
'income' => $totalIncomeAccount,
|
|
'expense' => $totalExpenseAccount,
|
|
'balance' => $finalBalanceAccount
|
|
];
|
|
}
|
|
}
|
|
|
|
|
|
// 각 계좌별 최종 잔액 정보를 담을 배열 초기화
|
|
$accountFinalBalances = [];
|
|
if (is_array($accounts)) {
|
|
foreach ($accounts as $index => $account) {
|
|
$accountDisplay = $account['company'] . ' ' . $account['number'];
|
|
if (!empty($account['memo'])) {
|
|
$accountDisplay .= ' (' . $account['memo'] . ')';
|
|
}
|
|
|
|
// 계좌별 전체 기간의 최종 잔액 계산
|
|
$balanceSql = "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 {$DB}.account_juil
|
|
WHERE is_deleted = '0' AND bankbook = :bankbook";
|
|
|
|
$balanceStmh = $pdo->prepare($balanceSql);
|
|
$balanceStmh->bindParam(':bankbook', $accountDisplay);
|
|
$balanceStmh->execute();
|
|
$balanceResult = $balanceStmh->fetch(PDO::FETCH_ASSOC);
|
|
|
|
// 계산된 정보를 배열에 저장
|
|
$accountFinalBalances[$index] = [
|
|
'name' => $accountDisplay,
|
|
'balance' => $balanceResult['balance'] ?? 0
|
|
];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 수입/지출 내역 조회를 위한 연도 설정
|
|
$year = isset($_REQUEST['year']) ? $_REQUEST['year'] : date('Y');
|
|
|
|
// 조회할 월 설정 (기본값: 현재 월)
|
|
$selectedMonth = isset($_REQUEST['startMonth']) ? $_REQUEST['startMonth'] : date('m');
|
|
|
|
// 계산서 발행 조회할 전달(이전 월) 계산
|
|
$prevMonth = $selectedMonth - 1;
|
|
if ($prevMonth == 0) {
|
|
$prevMonth = 12;
|
|
$year = $year - 1; // 연도가 바뀌는 경우 처리
|
|
}
|
|
|
|
// 이전달의 시작일과 종료일 설정
|
|
$prevStartDate = "$year-$prevMonth-01";
|
|
// $prevStartDate = date("Y-m-29", strtotime("-1 month", strtotime($prevStartDate)));
|
|
$prevEndDate = date("Y-m-t", strtotime($prevStartDate));
|
|
|
|
// 수입 내역 조회
|
|
$monthlySalesTable = $DB . '.monthly_sales';
|
|
|
|
$incomeSql = "
|
|
SELECT customer_name, invoice_issued, sales as amount, secondordnum, SUM(sales) as totalAmount
|
|
FROM {$monthlySalesTable}
|
|
WHERE invoice_issued = '발행'
|
|
AND closure_date BETWEEN :prevStartDate AND :prevEndDate
|
|
AND (is_deleted = '0' or is_deleted IS NULL)
|
|
GROUP BY customer_name
|
|
";
|
|
$incomeParams = [
|
|
':prevStartDate' => $prevStartDate,
|
|
':prevEndDate' => $prevEndDate
|
|
];
|
|
$incomeData = executeQuery($pdo, $incomeSql, $incomeParams);
|
|
|
|
// 미수금을 수입 내역에 추가
|
|
foreach ($receivables as $secondordnum => $receivableAmount) {
|
|
if ($receivableAmount > 0) {
|
|
$incomeData[] = [
|
|
'customer_name' => $vendorNames[$secondordnum],
|
|
'receivableAmount' => $receivableAmount,
|
|
'totalAmount' => $receivableAmount,
|
|
'secondordnum' => $secondordnum
|
|
];
|
|
}
|
|
}
|
|
|
|
// 미수금 누계 처리
|
|
$accumulatedIncomeData = [];
|
|
foreach ($incomeData as $item) {
|
|
$secondordnum = $item['secondordnum'];
|
|
|
|
// 누적 데이터에 secondordnum이 이미 존재하는지 확인
|
|
if (isset($accumulatedIncomeData[$secondordnum])) {
|
|
// 존재하면 금액을 누계
|
|
$accumulatedIncomeData[$secondordnum]['amount'] += isset($item['amount']) ? $item['amount'] : 0;
|
|
$accumulatedIncomeData[$secondordnum]['receivableAmount'] += isset($item['receivableAmount']) ? $item['receivableAmount'] : 0;
|
|
$accumulatedIncomeData[$secondordnum]['totalAmount'] += isset($item['totalAmount']) ? $item['totalAmount'] : 0;
|
|
} else {
|
|
// 존재하지 않으면 새로운 항목을 추가, 없는 필드 초기화
|
|
$accumulatedIncomeData[$secondordnum] = [
|
|
'customer_name' => $item['customer_name'],
|
|
'amount' => isset($item['amount']) ? $item['amount'] : 0,
|
|
'receivableAmount' => isset($item['receivableAmount']) ? $item['receivableAmount'] : 0,
|
|
'totalAmount' => isset($item['totalAmount']) ? $item['totalAmount'] : 0,
|
|
'secondordnum' => $secondordnum
|
|
];
|
|
}
|
|
}
|
|
|
|
// 배열을 다시 숫자 인덱스로 변환
|
|
$incomeData = array_values($accumulatedIncomeData);
|
|
|
|
// 현재 기준월의 1일부터 현재 날짜까지 수금된 데이터 조회
|
|
$fromdate = date("Y-m-01");
|
|
// 전전달의 29일 구하기
|
|
// $fromdate = date("Y-m-29", strtotime("-1 month", strtotime($fromdate)));
|
|
// $todate = date("Y-m-d");
|
|
// $lastmonthEnddate = date("Y-m-t", strtotime("first day of last month")); //전달의 **말일(마지막 날)**을
|
|
$lastmonthEnddate = date("Y-m-t");
|
|
|
|
// account_juil 테이블에서 해당 거래처의 수금된 항목 조회
|
|
$paymentSql = "
|
|
SELECT secondordnum, SUM(CAST(REPLACE(amount, ',', '') AS SIGNED)) AS total_payment
|
|
FROM {$accountTable}
|
|
WHERE inoutsep = '수입'
|
|
AND registDate BETWEEN :fromdate AND :lastmonthEnddate
|
|
AND (is_deleted IS NULL OR is_deleted = 0)
|
|
GROUP BY secondordnum
|
|
";
|
|
$paymentParams = [
|
|
':fromdate' => $fromdate,
|
|
':lastmonthEnddate' => $lastmonthEnddate
|
|
];
|
|
$paymentData = executeQuery($pdo, $paymentSql, $paymentParams);
|
|
|
|
// 이미 수금된 secondordnum을 배열로 저장
|
|
$receivedPayments = [];
|
|
foreach ($paymentData as $row) {
|
|
$secondordnum = $row['secondordnum'];
|
|
$totalPayment = (float)$row['total_payment'];
|
|
// $totalPayment = 0 ; // 이미 수금된것 제외 코드
|
|
|
|
// 수금 내역을 기록 (이미 수금된 금액이 있으면 배열에 추가) 수금이 계산서 금액과 일치하면 제외되는 알고리즘 추가한다.
|
|
if (intval($totalPayment) > 0) {
|
|
$receivedPayments[$secondordnum] = $totalPayment;
|
|
}
|
|
}
|
|
|
|
// echo '<pre>';
|
|
// print_r($paymentData);
|
|
// echo '</pre>';
|
|
|
|
// 수금된 금액을 차감한 나머지 미수금 계산
|
|
$filteredIncomeData = [];
|
|
foreach ($incomeData as $incomeRow) {
|
|
$secondordnum = $incomeRow['secondordnum'] ?? '';
|
|
|
|
// 수금된 금액이 있는지 확인
|
|
if (isset($receivedPayments[$secondordnum])) {
|
|
|
|
// echo ' , ' . $incomeRow['receivableAmount'] . ' 미수금 : ' . $receivedPayments[$secondordnum] ;
|
|
|
|
if( $incomeRow['receivableAmount'] == $receivedPayments[$secondordnum]) {
|
|
// 수금된 금액을 차감한 나머지 미수금을 계산
|
|
$remainingAmount = $incomeRow['receivableAmount'] - $receivedPayments[$secondordnum]; // 현재월의 수금내역 차감공식 수정 250415 찾음
|
|
}
|
|
else
|
|
{
|
|
$remainingAmount = $incomeRow['receivableAmount'] ;
|
|
}
|
|
|
|
// 나머지 미수금이 0보다 크거나 같으면 배열에 추가
|
|
if ($remainingAmount >= 0) {
|
|
$incomeRow['receivableAmount'] = $remainingAmount;
|
|
// totalAmount는 원래의 매출 금액으로 유지되므로 별도 할당 불필요
|
|
$filteredIncomeData[] = $incomeRow;
|
|
}
|
|
} else {
|
|
// 수금된 금액이 없으면 그대로 추가
|
|
$filteredIncomeData[] = $incomeRow;
|
|
}
|
|
}
|
|
|
|
// vendorNames 기준으로 $filteredIncomeData 정렬
|
|
usort($filteredIncomeData, function ($a, $b) use ($vendorNames) {
|
|
return strcmp($vendorNames[$a['secondordnum']], $vendorNames[$b['secondordnum']]);
|
|
});
|
|
|
|
// 제외된 항목 파일 경로
|
|
$excludedItemsFile = $_SERVER['DOCUMENT_ROOT'] . "/account_plan_juil/excluded_items.json";
|
|
|
|
// 제외된 항목 로드
|
|
$excludedItems = [];
|
|
if (file_exists($excludedItemsFile)) {
|
|
$excludedItems = json_decode(file_get_contents($excludedItemsFile), true);
|
|
}
|
|
|
|
// 디버깅을 위한 코드 추가
|
|
// echo '<pre>--- 제외된 항목 ($excludedItems) ---';
|
|
// print_r($excludedItems);
|
|
// echo '</pre>';
|
|
|
|
// $filteredIncomeData에서 제외된 항목 필터링
|
|
$filteredIncomeDataForDebugging = $filteredIncomeData; // 디버깅을 위해 원본 배열 복사
|
|
if (!empty($excludedItems)) {
|
|
$filteredIncomeData = array_filter($filteredIncomeData, function ($item) use ($excludedItems, $selectedYearMonth) {
|
|
// 디버깅을 위한 코드 추가
|
|
// echo '<pre>--- 현재 비교 중인 항목 ($item) ---';
|
|
// print_r($item);
|
|
// echo '</pre>';
|
|
|
|
$isExcluded = false; // 제외 여부 플래그
|
|
foreach ($excludedItems as $excludedItem) {
|
|
// 디버깅을 위한 코드 추가
|
|
// echo '<pre>--- 제외된 항목과 비교 ($excludedItem) ---';
|
|
// print_r($excludedItem);
|
|
// echo '--- 비교 결과 ---';
|
|
// echo '$excludedItem[\'yearMonth\'] === $selectedYearMonth : ' . ($excludedItem['yearMonth'] === $selectedYearMonth ? 'true' : 'false') . '<br>';
|
|
// echo '$excludedItem[\'customerName\'] === $item[\'customer_name\'] : ' . ($excludedItem['customerName'] === $item['customer_name'] ? 'true' : 'false') . '<br>';
|
|
// echo 'intval($excludedItem[\'amount\']) === intval($item[\'totalAmount\']) : ' . (intval($excludedItem['amount']) === intval($item['totalAmount']) ? 'true' : 'false') . '<br>';
|
|
// echo '</pre>';
|
|
|
|
if (
|
|
$excludedItem['yearMonth'] === $selectedYearMonth &&
|
|
$excludedItem['customerName'] === $item['customer_name'] &&
|
|
intval($excludedItem['amount']) === intval($item['totalAmount'])
|
|
) {
|
|
$isExcluded = true; // 제외 플래그 설정
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 디버깅을 위한 코드 추가
|
|
// echo '<pre>--- 최종 제외 여부: ' . ($isExcluded ? '제외됨' : '유지됨') . ' ---</pre>';
|
|
|
|
return !$isExcluded; // 제외 여부에 따라 반환
|
|
});
|
|
}
|
|
|
|
// 디버깅을 위한 코드 추가: 필터링 전후 비교
|
|
// echo '<pre>--- 필터링 전 ($filteredIncomeDataForDebugging) ---';
|
|
// print_r($filteredIncomeDataForDebugging);
|
|
// echo '</pre>';
|
|
|
|
// echo '<pre>--- 필터링 후 ($filteredIncomeData) ---';
|
|
// print_r($filteredIncomeData);
|
|
// echo '</pre>';
|
|
|
|
// ... (나머지 코드는 동일) ...
|
|
|
|
// 이번 달로 설정, 계산서는 지난달
|
|
$startMonth = isset($_REQUEST['startMonth']) ? $_REQUEST['startMonth'] : date('m');
|
|
$endMonth = isset($_REQUEST['endMonth']) ? $_REQUEST['endMonth'] : date('m');
|
|
|
|
$startDate = "$year-$startMonth-01";
|
|
$endDate = date("Y-m-t", strtotime("$year-$endMonth-01"));
|
|
|
|
// 지출 내역 조회 (월별 누적)
|
|
$accountPlanTable = 'account_plan_juil';
|
|
|
|
$expenseSql = "
|
|
SELECT content, SUM(amount) as totalAmount
|
|
FROM $DB.$accountPlanTable
|
|
WHERE inoutsep = '지출'
|
|
AND (is_deleted = '0' or is_deleted IS NULL)
|
|
GROUP BY content
|
|
";
|
|
|
|
$expenseData = executeQuery($pdo, $expenseSql);
|
|
|
|
// 지출 상세 내역 조회
|
|
$expenseDetailSql = "
|
|
SELECT num, content, amount, registDate, memo, ForeDate, approvalRequest, eworksNum, workDone
|
|
FROM $DB.$accountPlanTable
|
|
WHERE inoutsep = '지출'
|
|
AND (is_deleted = '0' or is_deleted IS NULL)
|
|
";
|
|
|
|
$expenseDetailData = executeQuery($pdo, $expenseDetailSql);
|
|
|
|
// 수입/지출 합계 및 순이익 계산 (필터링된 데이터 사용)
|
|
$filteredTotalIncome = array_sum(array_column($filteredIncomeData, 'totalAmount'));
|
|
$filteredTotalExpense = array_sum(array_column($expenseData, 'totalAmount'));
|
|
$filteredNetIncome = $filteredTotalIncome - $filteredTotalExpense + $finalBalance;
|
|
|
|
// receivableAmount 또는 totalAmount 값이 0보다 큰 항목만 필터링
|
|
$filteredIncomeData = array_filter($filteredIncomeData, function ($item) {
|
|
return (isset($item['receivableAmount']) && intval($item['receivableAmount']) > 0) ||
|
|
(isset($item['totalAmount']) && intval($item['totalAmount']) > 0);
|
|
});
|
|
|
|
// vendorNames 기준으로 $filteredIncomeData 정렬
|
|
usort($filteredIncomeData, function ($a, $b) {
|
|
return strcmp($a['customer_name'], $b['customer_name']);
|
|
});
|
|
|
|
// 수금을 바로 하는 업체를 처리하기 위한 코드
|
|
// 현재 날짜로부터 2개월 전의 1일을 시작일로 설정
|
|
$specialStartDate = (new DateTime('first day of -2 months'))->format('Y-m-d');
|
|
$specialEndDate = date("Y-m-d");
|
|
|
|
// account_juil 테이블에서 해당 거래처의 수금된 항목 조회
|
|
$specialPaymentSql = "
|
|
SELECT secondordnum, SUM(CAST(REPLACE(amount, ',', '') AS SIGNED)) AS total_payment
|
|
FROM {$accountTable}
|
|
WHERE inoutsep = '수입'
|
|
AND registDate BETWEEN :specialStartDate AND :specialEndDate
|
|
AND (is_deleted IS NULL OR is_deleted = 0)
|
|
GROUP BY secondordnum
|
|
";
|
|
$specialPaymentParams = [
|
|
':specialStartDate' => $specialStartDate,
|
|
':specialEndDate' => $specialEndDate
|
|
];
|
|
$specialPaymentData = executeQuery($pdo, $specialPaymentSql, $specialPaymentParams);
|
|
|
|
// paymentDataSpecial 배열을 secondordnum을 키로 하는 형태로 재구성
|
|
$specialPaymentMap = [];
|
|
foreach ($specialPaymentData as $payment) {
|
|
$specialPaymentMap[$payment['secondordnum']] = intval($payment['total_payment']);
|
|
}
|
|
|
|
// filteredIncomeData에서 secondordnum과 total_payment가 일치하는 항목 제거
|
|
$filteredIncomeData = array_filter($filteredIncomeData, function ($item) use ($specialPaymentMap) {
|
|
// receivableAmount 또는 totalAmount 값이 있으면 intval로 변환
|
|
$item['receivableAmount'] = isset($item['receivableAmount']) ? intval($item['receivableAmount']) : 0;
|
|
$item['totalAmount'] = isset($item['totalAmount']) ? intval($item['totalAmount']) : 0;
|
|
|
|
// paymentDataSpecial에 secondordnum이 있고, totalAmount와 total_payment가 일치하면 해당 항목을 제거
|
|
if (isset($specialPaymentMap[$item['secondordnum']]) && $specialPaymentMap[$item['secondordnum']] === $item['totalAmount']) {
|
|
return false; // 제거
|
|
}
|
|
|
|
// 제거되지 않은 항목만 반환
|
|
return true;
|
|
});
|
|
|
|
// vendorNames 기준으로 $filteredIncomeData 정렬
|
|
usort($filteredIncomeData, function ($a, $b) {
|
|
return strcmp($a['customer_name'], $b['customer_name']);
|
|
});
|
|
|
|
|
|
// 예상지급일 오름차순 정렬 코드
|
|
|
|
// 1) $expenseDetailData 배열이 준비된 직후, 테이블 출력 전에 추가
|
|
usort($expenseDetailData, function($a, $b) {
|
|
// '0000-00-00' 혹은 빈 문자열은 매우 미래 날짜로 처리
|
|
$dateA = (!empty($a['ForeDate']) && $a['ForeDate'] !== '0000-00-00')
|
|
? strtotime($a['ForeDate'])
|
|
: PHP_INT_MAX;
|
|
$dateB = (!empty($b['ForeDate']) && $b['ForeDate'] !== '0000-00-00')
|
|
? strtotime($b['ForeDate'])
|
|
: PHP_INT_MAX;
|
|
return $dateA <=> $dateB;
|
|
});
|
|
|
|
?>
|
|
|
|
<form id="board_form" name="board_form" method="post" enctype="multipart/form-data">
|
|
<input type="hidden" id="mode" name="mode" value="<?= isset($mode) ? $mode : '' ?>">
|
|
<input type="hidden" id="num" name="num" value="<?= isset($num) ? $num : '' ?>">
|
|
<input type="hidden" id="tablename" name="tablename" value="<?= isset($tablename) ? $tablename : '' ?>">
|
|
<input type="hidden" id="startMonth" name="startMonth" value="<?= isset($startMonth) ? $startMonth : '' ?>">
|
|
<input type="hidden" id="endMonth" name="endMonth" value="<?= isset($endMonth) ? $endMonth : '' ?>">
|
|
|
|
<div class="container-fluid">
|
|
<div id="myModal" class="modal">
|
|
<div class="modal-content" style="width:800px;">
|
|
<div class="modal-header">
|
|
<span class="modal-title"><?= $title_message ?></span>
|
|
<span class="close">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="custom-card"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container-fluid mt-5">
|
|
<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="row mb-3">
|
|
<div class="d-flex justify-content-center align-items-center">
|
|
<select id="year" name="year" class="form-select w-auto me-2" style="font-size:0.8rem; height:32px;" disabled onchange="loadDetails()">
|
|
<?php for ($i = date('Y'); $i >= 2024; $i--) : ?>
|
|
<option value="<?= $i ?>" <?= ($year == $i) ? 'selected' : '' ?>><?= $i ?>년</option>
|
|
<?php endfor; ?>
|
|
</select>
|
|
|
|
<button id="newInBtn" type="button" class="btn btn-dark btn-sm mx-1"> <i class="bi bi-pencil-square"></i> 수입등록 </button>
|
|
<button id="expenditureBtn" type="button" class="btn btn-dark btn-sm mx-1"> <i class="bi bi-pencil-square"></i> 지출등록 </button>
|
|
<button id="excludedListBtn" type="button" class="btn btn-primary btn-sm mx-1"> <i class="bi bi-card-checklist"></i> 수입제외목록 </button>
|
|
<button type="button" class="btn btn-dark btn-sm mx-1" id="csvDownload"> <i class="bi bi-floppy-fill"></i> CSV </button>
|
|
<button id="bulkNewBtn" type="button" class="btn btn-info btn-sm mx-3"> <i class="bi bi-copy"></i> 선택항목 다음달로 복제 </button>
|
|
<button id="approvalRequestBtn" type="button" class="btn btn-success btn-sm mx-3"> <i class="bi bi-file-earmark-arrow-up"></i> 전자결재요청 </button>
|
|
<div class="form-check form-check-inline ms-3">
|
|
<input class="form-check-input" type="checkbox" id="excludeCompleted" checked>
|
|
<label class="form-check-label" for="excludeCompleted" style="font-size: 0.9rem;">
|
|
결재완료 제외
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex flex-wrap justify-content-start align-items-center mt-2 p-2 border rounded" style="gap: 10px;">
|
|
<strong id="accountBalanceToggleLabel" class="me-2" style="cursor:pointer;user-select:none;">
|
|
계좌 잔액
|
|
<i id="accountBalanceToggleIcon" class="bi bi-chevron-down" style="cursor:pointer;font-size:0.8em;" title="계좌잔액 보기/숨기기"></i>
|
|
</strong>
|
|
<div id="accountBalanceContainer" style="gap:10px;">
|
|
<?php foreach ($accountFinalBalances as $summary): ?>
|
|
<?php if ($summary['balance'] > 0): ?>
|
|
<div class="border rounded p-1" style="font-size: 0.9em;">
|
|
<span class="text-secondary"><?= htmlspecialchars($summary['name']) ?>:</span>
|
|
<span class="fw-bold ms-1"><?= number_format($summary['balance']) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<div class="row mb-4">
|
|
<table class="table table-hover" id="detailTable">
|
|
<thead class="table-info">
|
|
<tr>
|
|
<th colspan="2" class="text-center">수입</th>
|
|
<th colspan="8" class="text-center">지출</th>
|
|
</tr>
|
|
<tr>
|
|
<th class="text-center">항목</th>
|
|
<th class="text-center">금액</th>
|
|
<th class="text-center"><h6><span id="selectAllCheckbox" class="text-primary" style="cursor: pointer; user-select: none;"> ☑ </span> 선택 </h6> </th>
|
|
<th class="text-center">예상지급일</th>
|
|
<th class="text-center">항목</th>
|
|
<th class="text-center">금액</th>
|
|
<th class="text-center">전자결재요청</th>
|
|
<th class="text-center">결재번호</th>
|
|
<th class="text-center">결재상태</th>
|
|
<th class="text-center">비고</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
// 미수금, 계산서 업체를 분할 후 다시 합치는 로직 구현
|
|
$receivablesData = [];
|
|
$invoicesData = [];
|
|
|
|
// 이번 달의 시작일과 종료일 계산
|
|
$currentMonthStartDate = date('Y-m-01');
|
|
$currentMonthEndDate = date('Y-m-t');
|
|
|
|
// 기존 데이터에서 미수금 및 계산서 발행 금액을 분리
|
|
foreach ($filteredIncomeData as $incomeRow) {
|
|
// 미수금이 있는 경우 receivablesData 배열에 추가
|
|
if ($incomeRow['receivableAmount'] > 0) {
|
|
$receivablesData[] = [
|
|
'customer_name' => $incomeRow['customer_name'],
|
|
'receivableAmount' => $incomeRow['receivableAmount'],
|
|
'secondordnum' => $incomeRow['secondordnum']
|
|
];
|
|
}
|
|
|
|
// 계산서 발행 금액이 있는 경우 invoicesData 배열에 추가
|
|
if ($incomeRow['amount'] > 0) {
|
|
$invoicesData[] = [
|
|
'customer_name' => $incomeRow['customer_name'],
|
|
'amount' => $incomeRow['amount'],
|
|
'secondordnum' => $incomeRow['secondordnum']
|
|
];
|
|
}
|
|
}
|
|
|
|
// 데이터베이스에서 수동으로 입력된 자료 가져오기
|
|
$manualInputSql = "
|
|
SELECT content AS customer_name, amount, secondordnum, num
|
|
FROM {$DB}.{$accountPlanTable}_in_juil
|
|
WHERE registDate BETWEEN :currentMonthStartDate AND :currentMonthEndDate
|
|
AND (is_deleted = '0' or is_deleted IS NULL)
|
|
";
|
|
$manualInputParams = [
|
|
':currentMonthStartDate' => $currentMonthStartDate,
|
|
':currentMonthEndDate' => $currentMonthEndDate
|
|
];
|
|
$manualEntries = executeQuery($pdo, $manualInputSql, $manualInputParams);
|
|
|
|
// 수동으로 입력된 자료를 기존 데이터에 추가
|
|
if (!empty($manualEntries)) {
|
|
foreach ($manualEntries as $entry) {
|
|
// 계산서 발행 금액이 있는 경우 invoicesData 배열에 추가
|
|
if ($entry['amount'] > 0) {
|
|
$invoicesData[] = [
|
|
'customer_name' => '(수기) ' . $entry['customer_name'],
|
|
'amount' => $entry['amount'],
|
|
'secondordnum' => $entry['secondordnum'],
|
|
'num' => $entry['num']
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// 최종적으로 두 배열을 다시 합칩니다.
|
|
$finalIncomeData = array_merge($receivablesData, $invoicesData);
|
|
|
|
// 월별 수입/지출 합계 초기화
|
|
$monthlyTotalIncome = 0;
|
|
$monthlyTotalExpense = 0;
|
|
|
|
$maxRows = max(count($finalIncomeData), count($expenseDetailData));
|
|
for ($i = 0; $i < $maxRows; $i++) :
|
|
// 수입/미수금
|
|
$incomeContent = isset($finalIncomeData[$i]['customer_name']) ? $finalIncomeData[$i]['customer_name'] . (isset($finalIncomeData[$i]['receivableAmount']) ? ' (미수금)' : ' (계산서발행)') : '';
|
|
$incomeAmount = isset($finalIncomeData[$i]['receivableAmount']) && intval($finalIncomeData[$i]['receivableAmount']) > 0 ? $finalIncomeData[$i]['receivableAmount'] : (isset($finalIncomeData[$i]['amount']) ? $finalIncomeData[$i]['amount'] : 0);
|
|
|
|
// Income details
|
|
$incomeNum = isset($finalIncomeData[$i]['num']) ? $finalIncomeData[$i]['num'] : '';
|
|
|
|
// Expense details
|
|
$expenseNum = isset($expenseDetailData[$i]['num']) ? $expenseDetailData[$i]['num'] : '';
|
|
$expenseContent = isset($expenseDetailData[$i]['content']) ? $expenseDetailData[$i]['content'] : '';
|
|
$expenseAmount = isset($expenseDetailData[$i]['amount']) ? $expenseDetailData[$i]['amount'] : 0;
|
|
$expenseMemo = isset($expenseDetailData[$i]['memo']) ? $expenseDetailData[$i]['memo'] : '';
|
|
$expenseForeDate = isset($expenseDetailData[$i]['ForeDate']) && $expenseDetailData[$i]['ForeDate'] !== '0000-00-00' ? $expenseDetailData[$i]['ForeDate'] : '';
|
|
$approvalRequest = isset($expenseDetailData[$i]['approvalRequest']) ? (!empty($expenseDetailData[$i]['approvalRequest']) ? '✅' : '') : '';
|
|
|
|
// 변경된 코드
|
|
$eworksNum = isset($expenseDetailData[$i]['eworksNum']) ? $expenseDetailData[$i]['eworksNum'] : '';
|
|
if ($eworksNum) {
|
|
// eworks 테이블에서 해당 num의 status 조회
|
|
$statusStmt = $pdo->prepare("SELECT status FROM {$DB}.eworks WHERE num = ? LIMIT 1");
|
|
$statusStmt->execute([ $eworksNum ]);
|
|
$row = $statusStmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
// status 가 'end' 면 완료, 아니면 공백
|
|
$workdone = (isset($row['status']) && $row['status'] === 'end') ? '완료' : '';
|
|
} else {
|
|
$workdone = '';
|
|
}
|
|
|
|
// 월별 수입/지출 합계
|
|
$monthlyTotalIncome += floatval($incomeAmount);
|
|
$monthlyTotalExpense += floatval($expenseAmount);
|
|
|
|
// 미수금 항목에 'text-danger' 클래스 추가
|
|
$incomeClass = (strpos($incomeContent, '미수금') !== false) ? 'text-danger' : '';
|
|
|
|
echo '<tr >';
|
|
echo '<td class="text-start fw-bold ' . $incomeClass . ' income-row" data-year-month="' . $selectedYearMonth . '" data-customer-name="' . htmlspecialchars($incomeContent) . '" data-amount="' . $incomeAmount . '">' . $incomeContent . '</td>';
|
|
echo '<td class="text-end text-primary fw-bold income-row"'
|
|
. ' data-year-month="' . $selectedYearMonth . '"'
|
|
. ' data-customer-name="' . htmlspecialchars($incomeContent) . '"'
|
|
. ' data-amount="' . $incomeAmount . '">'
|
|
. ($incomeAmount > 0 ? number_format($incomeAmount) : '')
|
|
. '</td>';
|
|
|
|
echo '<td><input type="checkbox" class="select-row" data-num="' . $expenseNum . '"></td>';
|
|
?>
|
|
<td class="text-center text-secondary fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $expenseForeDate ?></td>
|
|
<td class="text-start" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $expenseContent ?></td>
|
|
<td class="text-end text-danger fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');">
|
|
<?= floatval($expenseAmount) > 0 ? number_format((float)$expenseAmount) : '' ?>
|
|
</td>
|
|
<td class="text-center text-secondary fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $approvalRequest ?></td>
|
|
<td class="text-center text-secondary fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $eworksNum ?></td>
|
|
<td class="text-center text-secondary fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $workdone ?></td>
|
|
<td class="text-start text-secondary fw-bold" onclick="loadForm('update', '<?= $expenseNum ?>', 'account_plan_juil');"><?= $expenseMemo ?></td>
|
|
</tr>
|
|
|
|
<?php
|
|
// ↓ 여기서부터 소계 추가 로직
|
|
// 현재 행의 지출 예상지급일
|
|
$currentDate = $expenseDetailData[$i]['ForeDate'] ?? '';
|
|
// 다음 행의 지출 예상지급일 (존재하지 않으면 빈 문자열)
|
|
$nextDate = $expenseDetailData[$i+1]['ForeDate'] ?? '';
|
|
|
|
// 날짜가 유효하고('0000-00-00'은 빈값으로 처리) 그룹이 바뀌는 시점이라면
|
|
if (
|
|
!empty($currentDate) && $currentDate !== '0000-00-00'
|
|
&& $currentDate !== $nextDate
|
|
) {
|
|
// 해당 날짜의 합계 계산
|
|
$sum = 0;
|
|
foreach ($expenseDetailData as $erow) {
|
|
if ($erow['ForeDate'] === $currentDate) {
|
|
$sum += floatval(str_replace(',', '', $erow['amount']));
|
|
}
|
|
}
|
|
|
|
// 소계 행 출력
|
|
echo '<tr>';
|
|
// 수입 컬럼 영역 빈칸
|
|
echo '<td colspan="4"></td>';
|
|
// 4번째 컬럼: 날짜 소계 라벨
|
|
echo '<td class="text-end fw-bold table-secondary">'
|
|
. date('n/j', strtotime($currentDate))
|
|
. ' 소계</td>';
|
|
// 5번째 컬럼: 소계 금액
|
|
echo '<td class="text-end text-danger fw-bold table-secondary">'
|
|
. number_format($sum)
|
|
. '</td>';
|
|
// 6번째 컬럼: 빈칸
|
|
echo '<td></td>';
|
|
echo '</tr>';
|
|
}
|
|
// ↑ 소계 로직 끝
|
|
?>
|
|
<?php endfor; ?>
|
|
</tbody>
|
|
<tfoot class="">
|
|
<tr>
|
|
<th class="text-end table-secondary"> 수입 합계 </th>
|
|
<th class="text-end text-primary table-secondary"><?= number_format($monthlyTotalIncome) ?></th>
|
|
<th class="text-end table-secondary" colspan="3"> 지출 합계 </th>
|
|
<th class="text-end text-danger table-secondary"><?= number_format($monthlyTotalExpense) ?></th>
|
|
<th class="text-end" colspan="4"></th>
|
|
</tr>
|
|
<tr>
|
|
<th class="text-end table-secondary" colspan="5"> (계좌 잔액) </th>
|
|
<th class="text-end table-secondary"><?= number_format($finalBalance) ?></th>
|
|
<th class="text-end" colspan="4"></th>
|
|
</tr>
|
|
<tr>
|
|
<th class="text-end table-secondary" colspan="5"> 최종 차액 </th>
|
|
<th class="text-end table-secondary"><?= number_format($filteredNetIncome) ?></th>
|
|
<th class="text-end" colspan="4"></th>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container-fluid">
|
|
<div id="excludeModal" class="modal">
|
|
<div class="modal-content" style="width:400px;">
|
|
<div class="modal-header">
|
|
<span class="modal-title">항목 제외</span>
|
|
<span class="close">×</span>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p><H4> 이 항목을 제외하시겠습니까? </H4></p>
|
|
<button id="excludeConfirmBtn" class="btn btn-danger">제외</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="excludedListModal" class="modal">
|
|
<div class="modal-content" style="width: 600px;">
|
|
<div class="modal-header">
|
|
<span class="modal-title">수입제외목록</span>
|
|
<span class="close" onclick="$('#excludedListModal').hide();">×</span>
|
|
</div>
|
|
<div class="modal-body custom-modal-body">
|
|
<table class="table table-striped" id="excludedListTable">
|
|
<thead>
|
|
<tr>
|
|
<th>연월</th>
|
|
<th>거래처명</th>
|
|
<th>제외금액</th>
|
|
<th>복구</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<script>
|
|
let isSaving = false;
|
|
var ajaxRequest = null;
|
|
|
|
// 페이지 로딩
|
|
$(document).ready(function() {
|
|
var loader = document.getElementById('loadingOverlay');
|
|
if(loader) loader.style.display = 'none';
|
|
|
|
$("#expenditureBtn").on("click", function() {
|
|
loadForm('insert', null, '<?= $accountPlanTable ?>');
|
|
});
|
|
$("#newInBtn").on("click", function() {
|
|
loadForm('insert', null, '<?= $accountPlanTable ?>_in'); // 수입부분 등록
|
|
});
|
|
});
|
|
|
|
/*
|
|
* 폼을 로드하여 모달 창에 표시합니다.
|
|
*
|
|
* @param {string} mode 'insert' 또는 'update' 모드
|
|
* @param {number|null} num 수정할 레코드의 번호 (mode가 'update'일 때 사용)
|
|
* @param {string} tablename 데이터베이스 테이블 이름
|
|
*/
|
|
function loadForm(mode, num = null, Selecttablename = '') {
|
|
console.log(num);
|
|
|
|
if (mode == 'update' && (num == '' || num == null))
|
|
return // 번호가 없고 수정인 경우는 리턴
|
|
|
|
if (num == null) {
|
|
$("#mode").val('insert');
|
|
} else {
|
|
$("#mode").val('update');
|
|
$("#num").val(num);
|
|
}
|
|
|
|
if (ajaxRequest !== null) {
|
|
ajaxRequest.abort();
|
|
}
|
|
|
|
// 수입, 지출 테이블 구분
|
|
var url = (Selecttablename == '<?= $accountPlanTable ?>') ? "fetch_modal.php" : "fetch_in.php";
|
|
|
|
ajaxRequest = $.ajax({
|
|
type: "POST",
|
|
url: url,
|
|
data: {
|
|
mode: mode,
|
|
num: num,
|
|
tablename: Selecttablename // tablename 추가
|
|
},
|
|
dataType: "html",
|
|
success: function(response) {
|
|
document.querySelector(".modal-body .custom-card").innerHTML = response;
|
|
|
|
$("#myModal").show();
|
|
|
|
$(document).on("click", "#closeBtn", function() {
|
|
$("#myModal").hide();
|
|
});
|
|
|
|
$(document).on("click", "#saveBtn", function() {
|
|
if (isSaving) return; // 중복 저장 방지
|
|
isSaving = true;
|
|
|
|
if (ajaxRequest !== null) {
|
|
ajaxRequest.abort();
|
|
}
|
|
ajaxRequest = $.ajax({
|
|
url: "/account_plan_juil/insert.php",
|
|
type: "post",
|
|
data: {
|
|
mode: $("#mode").val(),
|
|
num: $("#num").val(),
|
|
update_log: $("#update_log").val(),
|
|
registDate: $("#registDate").val(),
|
|
inoutsep: $("input[name='inoutsep']:checked").val(),
|
|
content: $("#content").val(),
|
|
amount: $("#amount").val(),
|
|
memo: $("#memo").val(),
|
|
first_writer: $("#first_writer").val(),
|
|
content_detail: $("#content_detail").val(),
|
|
ForeDate: $("#ForeDate").val(),
|
|
secondordnum: $("#secondordnum").val(),
|
|
eworksNum: $("#eworksNum").val(),
|
|
workDone: $("#workDone").val(),
|
|
approvalRequest: $("#approvalRequest").val(),
|
|
tablename: Selecttablename // tablename 추가
|
|
},
|
|
dataType: "json",
|
|
success: function(response) {
|
|
Toastify({
|
|
text: "저장 완료",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#4fbe87",
|
|
}).showToast();
|
|
setTimeout(function() {
|
|
$("#myModal").hide();
|
|
location.reload();
|
|
}, 1500);
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log("AJAX Error: ", status, error);
|
|
},
|
|
complete: function() {
|
|
isSaving = false; // 저장 완료 후 중복 방지 해제
|
|
}
|
|
});
|
|
});
|
|
|
|
$(document).on("click", "#deleteBtn", function() {
|
|
var level = '<?= $_SESSION["level"] ?>';
|
|
|
|
if (level !== '1') {
|
|
Swal.fire({
|
|
title: '삭제불가',
|
|
text: "관리자만 삭제 가능합니다.",
|
|
icon: 'error',
|
|
confirmButtonText: '확인'
|
|
});
|
|
return;
|
|
}
|
|
|
|
Swal.fire({
|
|
title: '자료 삭제',
|
|
text: "삭제는 신중! 정말 삭제하시겠습니까?",
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#3085d6',
|
|
cancelButtonColor: '#d33',
|
|
confirmButtonText: '삭제',
|
|
cancelButtonText: '취소'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
$("#mode").val('delete');
|
|
var formData = $("#board_form").serialize();
|
|
formData += '&tablename=' + Selecttablename; // tablename 추가
|
|
|
|
$.ajax({
|
|
url: "/account_plan_juil/insert.php",
|
|
type: "post",
|
|
data: formData,
|
|
success: function(response) {
|
|
Toastify({
|
|
text: "삭제 완료",
|
|
duration: 2000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#4fbe87",
|
|
}).showToast();
|
|
|
|
$("#myModal").hide();
|
|
location.reload();
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log(jqxhr, status, error);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
$(".close").on("click", function() {
|
|
$("#myModal").hide();
|
|
});
|
|
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log("AJAX error in loadForm:", status, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 전화번호부 팝업 창을 엽니다.
|
|
*
|
|
* @param {string} searchfield 검색 필드 ID
|
|
*/
|
|
function phonebookBtn(searchfield) {
|
|
var search = $("#" + searchfield).val();
|
|
href = '../pb_juil/list.php?returnID=수입등록&search=' + search;
|
|
popupCenter(href, '전화번호 검색', 1600, 800);
|
|
}
|
|
|
|
// 기존 loadDetails 함수 유지
|
|
function loadDetails() {
|
|
const year = document.getElementById('year').value;
|
|
const startMonth = document.getElementById('startMonth').value;
|
|
const endMonth = document.getElementById('endMonth').value;
|
|
|
|
window.location.href = `list.php?year=${year}&startMonth=${startMonth}&endMonth=${endMonth}`;
|
|
}
|
|
|
|
/**
|
|
* 테이블 데이터를 CSV 파일로 다운로드합니다.
|
|
*/
|
|
document.getElementById("csvDownload").addEventListener("click", function() {
|
|
const table = document.getElementById("detailTable");
|
|
const theadRow = table.querySelector("thead tr");
|
|
const rows = table.querySelectorAll("tbody tr");
|
|
const tfootRow = table.querySelectorAll("tfoot tr");
|
|
|
|
const csvRows = [];
|
|
|
|
// Include the header row
|
|
const headerData = [];
|
|
theadRow.querySelectorAll("th").forEach(function(cell) {
|
|
headerData.push(cell.textContent);
|
|
});
|
|
csvRows.push(headerData.join(","));
|
|
|
|
// Include the data rows
|
|
rows.forEach(function(row) {
|
|
const rowData = [];
|
|
row.querySelectorAll("td").forEach(function(cell) {
|
|
// Remove commas from numeric values
|
|
const cleanValue = cell.textContent.replace(/,/g, "");
|
|
rowData.push(cleanValue);
|
|
});
|
|
csvRows.push(rowData.join(","));
|
|
});
|
|
|
|
// Include the data rows
|
|
tfootRow.forEach(function(row) {
|
|
const rowData = [];
|
|
row.querySelectorAll("th").forEach(function(cell) {
|
|
// Remove commas from numeric values
|
|
const cleanValue = cell.textContent.replace(/,/g, "");
|
|
rowData.push(cleanValue);
|
|
});
|
|
csvRows.push(rowData.join(","));
|
|
});
|
|
|
|
const csvContent = csvRows.join("\n");
|
|
// Add BOM for UTF-8 encoding to fix Korean characters
|
|
const blob = new Blob(['\ufeff' + csvContent], {
|
|
type: "text/csv;charset=utf-8;"
|
|
});
|
|
const link = document.createElement("a");
|
|
link.href = URL.createObjectURL(blob);
|
|
link.setAttribute("download", "월별_수입지출_예상내역서.csv");
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
});
|
|
|
|
|
|
|
|
// tr 클릭 이벤트 핸들러
|
|
$(document).on("click", ".income-row", function() {
|
|
let yearMonth = $(this).data("year-month");
|
|
let customerName = $(this).data("customer-name");
|
|
let amount = $(this).data("amount");
|
|
|
|
// data- 속성에서 값을 가져올 때, 해당 속성이 없으면 undefined가 반환되므로,
|
|
// 이 경우를 대비하여 기본값을 설정해주는 것이 좋습니다.
|
|
yearMonth = yearMonth || ''; // 빈 문자열 또는 원하는 기본값
|
|
customerName = customerName || ''; // 빈 문자열 또는 원하는 기본값
|
|
amount = amount || 0; // 0 또는 원하는 기본값
|
|
|
|
$("#excludeConfirmBtn").data("year-month", yearMonth);
|
|
$("#excludeConfirmBtn").data("customer-name", customerName);
|
|
$("#excludeConfirmBtn").data("amount", amount);
|
|
|
|
$("#excludeModal").show();
|
|
});
|
|
|
|
// 제외 버튼 클릭 이벤트 핸들러
|
|
$(document).on("click", "#excludeConfirmBtn", function() {
|
|
let yearMonth = $(this).data("year-month");
|
|
let customerName = $(this).data("customer-name");
|
|
let amount = $(this).data("amount");
|
|
|
|
$.ajax({
|
|
url: "/account_plan_juil/exclude_item.php",
|
|
type: "post",
|
|
data: {
|
|
yearMonth: yearMonth,
|
|
customerName: customerName,
|
|
amount: amount
|
|
},
|
|
dataType: "json",
|
|
success: function(response) {
|
|
if (response.success) {
|
|
Toastify({
|
|
text: "항목이 제외되었습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#4fbe87",
|
|
}).showToast();
|
|
|
|
// 화면에서 해당 tr 요소 제거
|
|
// $(`.income-row[data-year-month='${yearMonth}'][data-customer-name='${customerName}'][data-amount='${amount}']`).remove();
|
|
|
|
location.reload();
|
|
|
|
$("#excludeModal").hide();
|
|
} else {
|
|
Toastify({
|
|
text: "오류가 발생했습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#ff0000",
|
|
}).showToast();
|
|
}
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log("AJAX Error: ", status, error);
|
|
Toastify({
|
|
text: "서버와의 통신 중 오류가 발생했습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#ff0000",
|
|
}).showToast();
|
|
}
|
|
});
|
|
});
|
|
|
|
// 모달 닫기 버튼 이벤트
|
|
$(".close").on("click", function() {
|
|
$("#excludeModal").hide();
|
|
});
|
|
|
|
|
|
// "수입제외목록" 버튼 클릭 이벤트 핸들러
|
|
$(document).on("click", "#excludedListBtn", function() {
|
|
loadExcludedItems();
|
|
$("#excludedListModal").show();
|
|
});
|
|
|
|
|
|
function loadExcludedItems() {
|
|
$.ajax({
|
|
url: "/account_plan_juil/load_excluded_items.php",
|
|
type: "get",
|
|
dataType: "html",
|
|
success: function(response) {
|
|
$("#excludedListTable tbody").html(response);
|
|
|
|
// 복구 버튼 이벤트 핸들러 추가 (loadExcludedItems 함수 내부)
|
|
$(".restore-btn").on("click", function() {
|
|
let yearMonth = $(this).data("year-month");
|
|
let customerName = $(this).data("customer-name");
|
|
let amount = $(this).data("amount");
|
|
|
|
if (confirm("이 항목을 복구하시겠습니까?")) {
|
|
$.ajax({
|
|
url: "/account_plan_juil/restore_item.php",
|
|
type: "post",
|
|
data: {
|
|
yearMonth: yearMonth,
|
|
customerName: customerName,
|
|
amount: amount
|
|
},
|
|
dataType: "json",
|
|
success: function(response) {
|
|
if (response.success) {
|
|
Toastify({
|
|
text: "항목이 복구되었습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#4fbe87",
|
|
}).showToast();
|
|
|
|
// 복구 후 목록 다시 로드
|
|
loadExcludedItems();
|
|
// 복구 후에 페이지 새로 고침
|
|
location.reload();
|
|
} else {
|
|
Toastify({
|
|
text: "오류가 발생했습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#ff0000",
|
|
}).showToast();
|
|
}
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log("AJAX Error: ", status, error);
|
|
Toastify({
|
|
text: "서버와의 통신 중 오류가 발생했습니다.",
|
|
duration: 3000,
|
|
close: true,
|
|
gravity: "top",
|
|
position: "center",
|
|
backgroundColor: "#ff0000",
|
|
}).showToast();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
error: function(jqxhr, status, error) {
|
|
console.log("AJAX Error: ", status, error);
|
|
$("#excludedListTable tbody").html("<tr><td colspan='4'>목록을 불러오는 데 실패했습니다.</td></tr>");
|
|
}
|
|
});
|
|
}
|
|
|
|
$(document).ready(function(){
|
|
saveLogData('월별 수입지출 내역서');
|
|
|
|
// 전체 선택/해제 기능
|
|
$("#selectAllCheckbox").on("click", function() {
|
|
var isChecked = $(this).hasClass("selected");
|
|
|
|
if (isChecked) {
|
|
// 전체 해제 (보이는 행만)
|
|
$(".select-row:visible").prop("checked", false);
|
|
$(this).removeClass("selected").text(" ☑ ");
|
|
} else {
|
|
// 전체 선택 (보이는 행만)
|
|
$(".select-row:visible").prop("checked", true);
|
|
$(this).addClass("selected").text(" ☒ ");
|
|
}
|
|
});
|
|
|
|
// 개별 체크박스 변경 시 전체 선택 상태 업데이트
|
|
$(document).on("change", ".select-row", function() {
|
|
updateSelectAllCheckbox();
|
|
});
|
|
|
|
// 결재완료 제외 체크박스 이벤트
|
|
$("#excludeCompleted").on("change", function() {
|
|
filterCompletedRows();
|
|
});
|
|
|
|
// 페이지 로드 시 초기 필터링 적용
|
|
filterCompletedRows();
|
|
|
|
// 계좌잔액 토글 기능 초기화
|
|
initAccountBalanceToggle();
|
|
});
|
|
|
|
// 결재완료 제외 필터링 함수
|
|
function filterCompletedRows() {
|
|
var excludeCompleted = $("#excludeCompleted").is(":checked");
|
|
|
|
// 원본 데이터 저장 (처음 한 번만)
|
|
if (!window.originalTableData) {
|
|
window.originalTableData = [];
|
|
$("#detailTable tbody tr").each(function() {
|
|
var $row = $(this);
|
|
var $firstTd = $row.find('td').first();
|
|
|
|
window.originalTableData.push({
|
|
html: $row.prop('outerHTML'),
|
|
isSubtotal: $firstTd.attr('colspan') === '4', // 소계 행은 첫 번째 td에 colspan="4"가 있음
|
|
hasStatus: $row.find('td').eq(8).length > 0 && $row.find('td').eq(8).text().trim() !== '', // 결재상태 컬럼이 있고 내용이 있는지 확인
|
|
date: $row.find('td').eq(3).text().trim(), // 예상지급일 (소계 계산용)
|
|
amount: $row.find('td').eq(5).text().trim() // 금액 (소계 계산용)
|
|
});
|
|
});
|
|
}
|
|
|
|
// 테이블 내용 초기화
|
|
var $tbody = $("#detailTable tbody");
|
|
$tbody.empty();
|
|
|
|
// 필터링된 데이터 수집
|
|
var filteredRows = [];
|
|
|
|
window.originalTableData.forEach(function(rowData) {
|
|
if (rowData.isSubtotal) {
|
|
// 소계 행은 건너뛰고 나중에 새로 계산
|
|
return;
|
|
} else if (rowData.hasStatus) {
|
|
// 결재상태가 있는 행만 필터링 적용
|
|
var $tempRow = $(rowData.html);
|
|
var statusText = $tempRow.find("td").eq(8).text().trim();
|
|
|
|
if (!excludeCompleted || statusText !== "완료") {
|
|
filteredRows.push(rowData);
|
|
}
|
|
} else {
|
|
// 결재상태가 없는 행은 항상 표시
|
|
filteredRows.push(rowData);
|
|
}
|
|
});
|
|
|
|
// 필터링된 데이터를 날짜별로 정렬하여 처리
|
|
var processedRows = [];
|
|
|
|
// 날짜가 있는 행들과 없는 행들을 분리
|
|
var rowsWithDate = [];
|
|
var rowsWithoutDate = [];
|
|
|
|
filteredRows.forEach(function(rowData) {
|
|
if (rowData.date && rowData.date !== '' && rowData.date !== '0000-00-00') {
|
|
rowsWithDate.push(rowData);
|
|
} else {
|
|
rowsWithoutDate.push(rowData);
|
|
}
|
|
});
|
|
|
|
// 날짜가 있는 행들을 날짜순으로 정렬
|
|
rowsWithDate.sort(function(a, b) {
|
|
return new Date(a.date) - new Date(b.date);
|
|
});
|
|
|
|
// 날짜별로 그룹화하여 처리
|
|
var currentDate = '';
|
|
var currentDateRows = [];
|
|
|
|
rowsWithDate.forEach(function(rowData) {
|
|
if (currentDate !== rowData.date) {
|
|
// 이전 날짜의 행들과 소계 처리
|
|
if (currentDateRows.length > 0) {
|
|
// 이전 날짜의 행들 추가
|
|
currentDateRows.forEach(function(prevRowData) {
|
|
var $tempRow = $(prevRowData.html);
|
|
processedRows.push($tempRow);
|
|
});
|
|
|
|
// 이전 날짜의 소계 추가
|
|
var subtotalAmount = calculateSubtotalForDate(currentDate, currentDateRows);
|
|
if (subtotalAmount > 0) {
|
|
var subtotalRow = createSubtotalRow(currentDate, subtotalAmount);
|
|
processedRows.push(subtotalRow);
|
|
}
|
|
}
|
|
|
|
// 새로운 날짜 시작
|
|
currentDate = rowData.date;
|
|
currentDateRows = [rowData];
|
|
} else {
|
|
// 같은 날짜의 행 추가
|
|
currentDateRows.push(rowData);
|
|
}
|
|
});
|
|
|
|
// 마지막 날짜의 행들과 소계 처리
|
|
if (currentDateRows.length > 0) {
|
|
currentDateRows.forEach(function(rowData) {
|
|
var $tempRow = $(rowData.html);
|
|
processedRows.push($tempRow);
|
|
});
|
|
|
|
var subtotalAmount = calculateSubtotalForDate(currentDate, currentDateRows);
|
|
if (subtotalAmount > 0) {
|
|
var subtotalRow = createSubtotalRow(currentDate, subtotalAmount);
|
|
processedRows.push(subtotalRow);
|
|
}
|
|
}
|
|
|
|
// 날짜가 없는 행들 추가 (수입 행들)
|
|
rowsWithoutDate.forEach(function(rowData) {
|
|
var $tempRow = $(rowData.html);
|
|
processedRows.push($tempRow);
|
|
});
|
|
|
|
// 최종 테이블에 추가
|
|
processedRows.forEach(function($row) {
|
|
$tbody.append($row);
|
|
});
|
|
|
|
// 전체 선택 체크박스 상태 업데이트
|
|
updateSelectAllCheckbox();
|
|
}
|
|
|
|
// 특정 날짜의 소계 계산 함수
|
|
function calculateSubtotalForDate(date, rows) {
|
|
var total = 0;
|
|
rows.forEach(function(rowData) {
|
|
var amountText = rowData.amount.replace(/,/g, '');
|
|
var amount = parseFloat(amountText) || 0;
|
|
total += amount;
|
|
});
|
|
return total;
|
|
}
|
|
|
|
// 소계 행 생성 함수
|
|
function createSubtotalRow(date, amount) {
|
|
// 날짜 형식 파싱 개선 (YYYY-MM-DD 형식)
|
|
var dateParts = date.split('-');
|
|
var year = parseInt(dateParts[0]);
|
|
var month = parseInt(dateParts[1]);
|
|
var day = parseInt(dateParts[2]);
|
|
|
|
// 날짜 유효성 검사
|
|
if (isNaN(year) || isNaN(month) || isNaN(day)) {
|
|
console.warn('Invalid date format:', date);
|
|
return $('<tr></tr>'); // 빈 행 반환
|
|
}
|
|
|
|
var subtotalHtml = '<tr>' +
|
|
'<td colspan="4"></td>' +
|
|
'<td class="text-end fw-bold table-secondary">' + month + '/' + day + ' 소계</td>' +
|
|
'<td class="text-end text-danger fw-bold table-secondary">' + amount.toLocaleString() + '</td>' +
|
|
'<td></td>' +
|
|
'</tr>';
|
|
|
|
return $(subtotalHtml);
|
|
}
|
|
|
|
// 전체 선택 체크박스 상태 업데이트 함수
|
|
function updateSelectAllCheckbox() {
|
|
var visibleCheckboxes = $(".select-row:visible").length;
|
|
var checkedVisibleCheckboxes = $(".select-row:visible:checked").length;
|
|
|
|
if (checkedVisibleCheckboxes === 0) {
|
|
$("#selectAllCheckbox").removeClass("selected").text(" ☑ ");
|
|
} else if (checkedVisibleCheckboxes === visibleCheckboxes) {
|
|
$("#selectAllCheckbox").addClass("selected").text(" ☒ ");
|
|
} else {
|
|
$("#selectAllCheckbox").removeClass("selected").text(" ☑ ");
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
$(function(){
|
|
|
|
// 1) 컬럼 정의
|
|
const columns = [
|
|
{ id: 'registDate_bulk', label: '등록일자', type: 'date', width: 130, align: 'center' },
|
|
{ id: 'inoutsep_bulk', label: '구분', type: 'text', width: 80, align: 'center', value: '지출' },
|
|
{ id: 'content_bulk', label: '내역', type: 'text', width: 220, align: 'left' },
|
|
{ id: 'amount_bulk', label: '금액', type: 'text', width: 150, align: 'right', function: inputNumberFormat },
|
|
{ id: 'ForeDate_bulk', label: '예상지급일', type: 'date', width: 130, align: 'center' },
|
|
{ id: 'memo_bulk', label: '비고', type: 'text', width: 220, align: 'left' }
|
|
];
|
|
|
|
|
|
// 1) 저장 콜백 함수 정의
|
|
function saveBulk(entries) {
|
|
if (!entries.length) {
|
|
return Swal.fire('입력 없음', '저장할 데이터가 없습니다.', 'info');
|
|
}
|
|
console.log(entries);
|
|
Swal.fire({
|
|
title: '전체 저장',
|
|
text: `총 ${entries.length}건을 저장하시겠습니까?`,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: '저장',
|
|
cancelButtonText: '취소'
|
|
}).then(result => {
|
|
if (!result.isConfirmed) return;
|
|
|
|
// AJAX 요청
|
|
$.ajax({
|
|
url: '/account_plan_juil/copy_to_next_month.php',
|
|
type: 'POST',
|
|
data: JSON.stringify({ entries }), // { entries: [ {registDate, ...}, ... ] }
|
|
contentType: 'application/json; charset=utf-8',
|
|
dataType: 'json'
|
|
})
|
|
.done(res => {
|
|
if (res.status === 'success') {
|
|
const count = res.insertedCount ?? entries.length;
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: `${count}건 저장 완료`,
|
|
showConfirmButton: false,
|
|
timer: 1500
|
|
}).then(() => location.reload());
|
|
} else {
|
|
Swal.fire('저장 실패', res.message || '오류가 발생했습니다.', 'error');
|
|
}
|
|
})
|
|
.fail((xhr, status, err) => {
|
|
console.error('Bulk insert AJAX Error:', status, err);
|
|
Swal.fire('서버 오류', '서버와의 통신 중 오류가 발생했습니다.', 'error');
|
|
});
|
|
});
|
|
}
|
|
|
|
// 3) 버튼 클릭 시 모달 열기
|
|
// 대량등록 모달 열기 + 선택된 행 데이터 전달
|
|
$('#bulkNewBtn').on('click', function() {
|
|
const items = [];
|
|
|
|
// 체크된 각 행에서 데이터 추출
|
|
$('.select-row:checked').each(function() {
|
|
const $tr = $(this).closest('tr');
|
|
// 1) 현재 예상지급일
|
|
const currentDate = $tr.find('td').eq(3).text().trim();
|
|
|
|
// 2) 다음달 날짜 계산
|
|
let nextMonthDate = '';
|
|
if (currentDate) {
|
|
const parts = currentDate.split('-');
|
|
if (parts.length === 3) {
|
|
let [year, month, day] = parts.map(Number);
|
|
month++;
|
|
if (month > 12) {
|
|
month = 1;
|
|
year++;
|
|
}
|
|
nextMonthDate =
|
|
year + '-' +
|
|
String(month).padStart(2, '0') + '-' +
|
|
String(day).padStart(2, '0');
|
|
}
|
|
}
|
|
|
|
// 3) 객체에 담기 (컬럼 정의에 맞춰 키 이름 _bulk 포함)
|
|
items.push({
|
|
registDate_bulk: currentDate,
|
|
inoutsep_bulk: '지출', // 구분
|
|
content_bulk: $tr.find('td').eq(4).text().trim(), // 내역
|
|
amount_bulk: $tr.find('td').eq(5).text().trim(), // 금액
|
|
ForeDate_bulk: nextMonthDate,
|
|
memo_bulk: $tr.find('td').eq(9).text().replace(/,/g, '') // 비고
|
|
});
|
|
});
|
|
|
|
// 선택 없으면 경고
|
|
if (!items.length) {
|
|
return Swal.fire('선택 없음', '하나 이상 선택해 주세요.', 'warning');
|
|
}
|
|
|
|
// 모달 띄우기 (폭·여백 옵션과 초기 데이터 전달)
|
|
BulkModal.open(
|
|
columns,
|
|
saveBulk,
|
|
{ width: 1400, maxWidth: '90vw', margin: '30px auto' },
|
|
items
|
|
);
|
|
});
|
|
|
|
|
|
$('#approvalRequestBtn').on('click', function(){
|
|
var items = [];
|
|
|
|
// 체크된 각 행에서 필요한 값 수집
|
|
$('.select-row:checked').each(function(){
|
|
var $tr = $(this).closest('tr');
|
|
items.push({
|
|
num: $(this).data('num'), // 고유번호
|
|
payDate: $tr.find('td').eq(3).text().trim(), // 예상지급일 (4번째 td)
|
|
content: $tr.find('td').eq(4).text().trim(), // 항목 (5번째 td)
|
|
amount: parseFloat( // 금액 (6번째 td)
|
|
$tr.find('td').eq(5).text().replace(/,/g, '')
|
|
) || 0
|
|
});
|
|
});
|
|
|
|
if (items.length === 0) {
|
|
alert('하나 이상 선택해 주세요.');
|
|
return;
|
|
}
|
|
|
|
console.log(JSON.stringify(items) );
|
|
// JSON 형태로 AJAX 요청
|
|
$.ajax({
|
|
url: '/account_plan_juil/eworks_process.php',
|
|
type: 'POST',
|
|
data: JSON.stringify({ items: items }),
|
|
contentType: 'application/json; charset=utf-8',
|
|
dataType: 'json'
|
|
})
|
|
.done(function(res){
|
|
if (res.success) {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: '결재 요청이 전송되었습니다.',
|
|
showConfirmButton: false,
|
|
timer: 1000
|
|
}).then(() => {
|
|
location.reload();
|
|
});
|
|
} else {
|
|
alert('결재 요청 실패: ' + res.message);
|
|
}
|
|
})
|
|
.fail(function(xhr, status, err){
|
|
console.error('AJAX Error:', status, err);
|
|
alert('서버 오류가 발생했습니다.');
|
|
});
|
|
});
|
|
|
|
// 선택항목 다음달로 복제 기능
|
|
function copyNextMonth(){
|
|
|
|
var items = [];
|
|
|
|
// 체크된 각 행에서 필요한 값 수집
|
|
$('.select-row:checked').each(function(){
|
|
var $tr = $(this).closest('tr');
|
|
var currentDate = $tr.find('td').eq(3).text().trim(); // 현재 예상지급일
|
|
|
|
// 다음달 날짜 계산
|
|
var nextMonthDate = '';
|
|
if (currentDate && currentDate !== '') {
|
|
var dateParts = currentDate.split('-');
|
|
if (dateParts.length === 3) {
|
|
var year = parseInt(dateParts[0]);
|
|
var month = parseInt(dateParts[1]);
|
|
var day = parseInt(dateParts[2]);
|
|
|
|
// 다음달 계산
|
|
month++;
|
|
if (month > 12) {
|
|
month = 1;
|
|
year++;
|
|
}
|
|
|
|
// 날짜 형식 맞추기 (YYYY-MM-DD)
|
|
nextMonthDate = year + '-' +
|
|
(month < 10 ? '0' + month : month) + '-' +
|
|
(day < 10 ? '0' + day : day);
|
|
}
|
|
}
|
|
|
|
items.push({
|
|
num: $(this).data('num'), // 고유번호
|
|
payDate: currentDate, // 현재 예상지급일
|
|
nextMonthDate: nextMonthDate, // 다음달 예상지급일
|
|
content: $tr.find('td').eq(4).text().trim(), // 항목 (5번째 td)
|
|
amount: parseFloat( // 금액 (6번째 td)
|
|
$tr.find('td').eq(5).text().replace(/,/g, '')
|
|
) || 0
|
|
});
|
|
});
|
|
|
|
if (items.length === 0) {
|
|
alert('하나 이상 선택해 주세요.');
|
|
return;
|
|
}
|
|
// 확인 다이얼로그
|
|
Swal.fire({
|
|
title: '다음달로 복제',
|
|
text: `선택된 ${items.length}개 항목을 다음달로 복제하시겠습니까?`,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#3085d6',
|
|
cancelButtonColor: '#d33',
|
|
confirmButtonText: '복제',
|
|
cancelButtonText: '취소'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
// JSON 형태로 AJAX 요청
|
|
$.ajax({
|
|
url: '/account_plan_juil/copy_to_next_month.php',
|
|
type: 'POST',
|
|
data: JSON.stringify({ items: items }),
|
|
contentType: 'application/json; charset=utf-8',
|
|
dataType: 'json'
|
|
})
|
|
.done(function(res){
|
|
console.log('Response received:', res); // 디버깅용 로그 추가
|
|
|
|
if (res.success) {
|
|
var copiedCount = res.copiedCount || 0; // 기본값 설정
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: '복제가 완료되었습니다.',
|
|
text: `${copiedCount}개 항목이 다음달로 복제되었습니다.`,
|
|
showConfirmButton: false,
|
|
timer: 2000
|
|
}).then(() => {
|
|
location.reload();
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '복제 실패',
|
|
text: res.message || '복제 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
})
|
|
.fail(function(xhr, status, err){
|
|
console.error('AJAX Error:', status, err);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: '서버 오류',
|
|
text: '서버와의 통신 중 오류가 발생했습니다.'
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
// 계좌잔액 토글 기능
|
|
function initAccountBalanceToggle() {
|
|
const toggleBtn = document.getElementById('accountBalanceToggleIcon');
|
|
const label = document.getElementById('accountBalanceToggleLabel');
|
|
const container = document.getElementById('accountBalanceContainer');
|
|
if (!toggleBtn || !container || !label) return;
|
|
|
|
// 쿠키에서 저장된 상태 확인
|
|
const isVisible = getCookie('accountBalanceVisible_Plan') !== 'false';
|
|
|
|
// 초기 상태 설정
|
|
if (!isVisible) {
|
|
container.style.display = 'none';
|
|
toggleBtn.classList.remove('bi-chevron-down');
|
|
toggleBtn.classList.add('bi-chevron-right');
|
|
} else {
|
|
container.style.display = 'flex';
|
|
toggleBtn.classList.remove('bi-chevron-right');
|
|
toggleBtn.classList.add('bi-chevron-down');
|
|
}
|
|
|
|
function toggleBalance(e) {
|
|
e.stopPropagation();
|
|
const isCurrentlyVisible = container.style.display !== 'none';
|
|
if (isCurrentlyVisible) {
|
|
container.style.display = 'none';
|
|
toggleBtn.classList.remove('bi-chevron-down');
|
|
toggleBtn.classList.add('bi-chevron-right');
|
|
setCookie('accountBalanceVisible_Plan', 'false', 365);
|
|
} else {
|
|
container.style.display = 'flex';
|
|
toggleBtn.classList.remove('bi-chevron-right');
|
|
toggleBtn.classList.add('bi-chevron-down');
|
|
setCookie('accountBalanceVisible_Plan', 'true', 365);
|
|
}
|
|
}
|
|
|
|
toggleBtn.addEventListener('click', toggleBalance);
|
|
label.addEventListener('click', toggleBalance);
|
|
}
|
|
|
|
// 쿠키 설정 함수
|
|
function setCookie(name, value, days) {
|
|
let expires = "";
|
|
if (days) {
|
|
const date = new Date();
|
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
expires = "; expires=" + date.toUTCString();
|
|
}
|
|
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
|
}
|
|
|
|
// 쿠키 읽기 함수
|
|
function getCookie(name) {
|
|
const nameEQ = name + "=";
|
|
const ca = document.cookie.split(';');
|
|
for (let i = 0; i < ca.length; i++) {
|
|
let c = ca[i];
|
|
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|