修改
This commit is contained in:
@@ -39,7 +39,7 @@ def lock_fiscal_year(req: LockRequest):
|
|||||||
ON CONFLICT (fiscal_year)
|
ON CONFLICT (fiscal_year)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
is_locked = true,
|
is_locked = true,
|
||||||
locked_at = CURRENT_TIMESTAMP
|
locked_at = CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo'
|
||||||
""", (req.fiscal_year,))
|
""", (req.fiscal_year,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ ACCOUNT_GROUPS = [
|
|||||||
("負債合計", ["501", "502", "503", "504", "505", "506", "507", "508", "509"]),
|
("負債合計", ["501", "502", "503", "504", "505", "506", "507", "508", "509"]),
|
||||||
# 資本の部
|
# 資本の部
|
||||||
("資本金合計", ["601"]),
|
("資本金合計", ["601"]),
|
||||||
("利益剰余金合計", ["602"]),
|
# 利益剰余金合計は特別処理(602 + 当期純利益の内訳を表示)
|
||||||
("純資産合計", ["601", "602"])
|
("純資産合計", ["601", "602"])
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -145,6 +145,101 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
# 大区分合計用に総計を積算
|
# 大区分合計用に総計を積算
|
||||||
grand_total_closing += data["closing_balance"]
|
grand_total_closing += data["closing_balance"]
|
||||||
|
|
||||||
|
# 利益剰余金(602)の後に詳細を挿入
|
||||||
|
if code == "602":
|
||||||
|
# 繰越利益剰余金 = 602の期首残高
|
||||||
|
retained_earnings_opening = data["opening_balance"]
|
||||||
|
|
||||||
|
# 当期純損益金額を計算(収益 - 費用)
|
||||||
|
# 収益(7xx)の合計
|
||||||
|
revenue_total = D(0)
|
||||||
|
for acc_code, acc_data in account_data.items():
|
||||||
|
if acc_code.startswith("7"):
|
||||||
|
# 収益は貸方が増加、借方が減少
|
||||||
|
revenue_total += acc_data["credit"] - acc_data["debit"]
|
||||||
|
|
||||||
|
# 費用(8xx)の合計
|
||||||
|
expense_total = D(0)
|
||||||
|
for acc_code, acc_data in account_data.items():
|
||||||
|
if acc_code.startswith("8"):
|
||||||
|
# 費用は借方が増加、貸方が減少
|
||||||
|
expense_total += acc_data["debit"] - acc_data["credit"]
|
||||||
|
|
||||||
|
# 当期純損益金額 = 収益 - 費用
|
||||||
|
net_income = revenue_total - expense_total
|
||||||
|
|
||||||
|
# 繰越利益剰余金合計 = 期首繰越利益 + 当期純損益
|
||||||
|
retained_earnings_total = retained_earnings_opening + net_income
|
||||||
|
|
||||||
|
# 繰越利益の行を追加
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": " 繰越利益",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": str(retained_earnings_opening),
|
||||||
|
"debit": "0",
|
||||||
|
"credit": "0",
|
||||||
|
"closing_balance": str(retained_earnings_opening),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": False,
|
||||||
|
"indent": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# 当期純損益金額の行を追加
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": " 当期純損益金額",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": "0",
|
||||||
|
"debit": str(expense_total) if expense_total > 0 else "0",
|
||||||
|
"credit": str(revenue_total) if revenue_total > 0 else "0",
|
||||||
|
"closing_balance": str(net_income),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": False,
|
||||||
|
"indent": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# 繰越利益剰余金合計の行を追加
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": " 繰越利益剰余金合計",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": str(retained_earnings_opening),
|
||||||
|
"debit": str(expense_total) if expense_total > 0 else "0",
|
||||||
|
"credit": str(revenue_total) if revenue_total > 0 else "0",
|
||||||
|
"closing_balance": str(retained_earnings_total),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": True,
|
||||||
|
"indent": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# その他利益剰余金合計(現在は0)
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": " その他利益剰余金合計",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": "0",
|
||||||
|
"debit": "0",
|
||||||
|
"credit": "0",
|
||||||
|
"closing_balance": "0",
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": True,
|
||||||
|
"indent": True
|
||||||
|
})
|
||||||
|
|
||||||
|
# 利益剰余金合計の行を追加
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": "利益剰余金合計",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": str(retained_earnings_opening),
|
||||||
|
"debit": str(expense_total) if expense_total > 0 else "0",
|
||||||
|
"credit": str(revenue_total) if revenue_total > 0 else "0",
|
||||||
|
"closing_balance": str(retained_earnings_total),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": True
|
||||||
|
})
|
||||||
|
|
||||||
# 合計行を挿入(グループ定義の順序で)
|
# 合計行を挿入(グループ定義の順序で)
|
||||||
for group_name, group_codes in ACCOUNT_GROUPS:
|
for group_name, group_codes in ACCOUNT_GROUPS:
|
||||||
# このグループにまだ合計行を挿入していない
|
# このグループにまだ合計行を挿入していない
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ def update_journal_entry(journal_id: int, req: JournalEntryUpdateRequest):
|
|||||||
UPDATE journal_entries
|
UPDATE journal_entries
|
||||||
SET description = %s,
|
SET description = %s,
|
||||||
version = version + 1,
|
version = version + 1,
|
||||||
updated_at = NOW(),
|
updated_at = CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo',
|
||||||
updated_reason = %s
|
updated_reason = %s
|
||||||
WHERE journal_id = %s
|
WHERE journal_id = %s
|
||||||
""", (
|
""", (
|
||||||
@@ -472,7 +472,7 @@ def delete_journal_entry(journal_id: int, req: JournalEntryDeleteRequest):
|
|||||||
cur.execute("""
|
cur.execute("""
|
||||||
UPDATE journal_entries
|
UPDATE journal_entries
|
||||||
SET is_deleted = true,
|
SET is_deleted = true,
|
||||||
updated_at = NOW()
|
updated_at = CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo'
|
||||||
WHERE journal_entry_id = %s
|
WHERE journal_entry_id = %s
|
||||||
AND is_deleted = false
|
AND is_deleted = false
|
||||||
RETURNING journal_entry_id
|
RETURNING journal_entry_id
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def get_month_locks():
|
|||||||
fiscal_year,
|
fiscal_year,
|
||||||
month,
|
month,
|
||||||
is_locked,
|
is_locked,
|
||||||
locked_at,
|
(locked_at AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Tokyo' as locked_at,
|
||||||
locked_by
|
locked_by
|
||||||
FROM month_locks
|
FROM month_locks
|
||||||
ORDER BY fiscal_year, month
|
ORDER BY fiscal_year, month
|
||||||
@@ -39,11 +39,11 @@ def lock_month(fiscal_year: int, month: int):
|
|||||||
locked_at,
|
locked_at,
|
||||||
locked_by
|
locked_by
|
||||||
)
|
)
|
||||||
VALUES (%s, %s, true, NOW(), %s)
|
VALUES (%s, %s, true, CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo', %s)
|
||||||
ON CONFLICT (fiscal_year, month)
|
ON CONFLICT (fiscal_year, month)
|
||||||
DO UPDATE
|
DO UPDATE
|
||||||
SET is_locked = true,
|
SET is_locked = true,
|
||||||
locked_at = NOW(),
|
locked_at = CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo',
|
||||||
locked_by = EXCLUDED.locked_by
|
locked_by = EXCLUDED.locked_by
|
||||||
""", (
|
""", (
|
||||||
fiscal_year,
|
fiscal_year,
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ router = APIRouter(prefix="/year-locks", tags=["年度锁定"])
|
|||||||
def get_year_locks():
|
def get_year_locks():
|
||||||
with get_connection() as conn, conn.cursor() as cur:
|
with get_connection() as conn, conn.cursor() as cur:
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT fiscal_year, is_locked, locked_at, locked_by
|
SELECT fiscal_year, is_locked,
|
||||||
|
(locked_at AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Tokyo' as locked_at,
|
||||||
|
locked_by
|
||||||
FROM year_locks
|
FROM year_locks
|
||||||
ORDER BY fiscal_year
|
ORDER BY fiscal_year
|
||||||
""")
|
""")
|
||||||
@@ -20,11 +22,11 @@ def lock_year(fiscal_year: int):
|
|||||||
with get_connection() as conn, conn.cursor() as cur:
|
with get_connection() as conn, conn.cursor() as cur:
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO year_locks (fiscal_year, is_locked, locked_at, locked_by)
|
INSERT INTO year_locks (fiscal_year, is_locked, locked_at, locked_by)
|
||||||
VALUES (%s, true, NOW(), %s)
|
VALUES (%s, true, CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo', %s)
|
||||||
ON CONFLICT (fiscal_year)
|
ON CONFLICT (fiscal_year)
|
||||||
DO UPDATE
|
DO UPDATE
|
||||||
SET is_locked = true,
|
SET is_locked = true,
|
||||||
locked_at = NOW(),
|
locked_at = CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Tokyo',
|
||||||
locked_by = EXCLUDED.locked_by
|
locked_by = EXCLUDED.locked_by
|
||||||
""", (fiscal_year, "system"))
|
""", (fiscal_year, "system"))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
32
backend/insert_602_opening.py
Normal file
32
backend/insert_602_opening.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
os.environ['DB_HOST'] = '192.168.0.61'
|
||||||
|
os.environ['DB_PORT'] = '55432'
|
||||||
|
os.environ['DB_NAME'] = 'njts_acct'
|
||||||
|
os.environ['DB_USER'] = 'njts_app'
|
||||||
|
os.environ['DB_PASSWORD'] = 'njts_app2025'
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
# 602科目の前期残高を登録
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO opening_balances (fiscal_year, account_id, opening_debit, opening_credit)
|
||||||
|
VALUES (2025, (SELECT account_id FROM accounts WHERE account_code = '602'), 0, 5206146)
|
||||||
|
ON CONFLICT (fiscal_year, account_id)
|
||||||
|
DO UPDATE SET opening_credit = 5206146
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
print('✓ 602科目(繰越利益剰余金)の前期残高 5,206,146円 を登録しました')
|
||||||
|
|
||||||
|
# 確認
|
||||||
|
cur.execute("""
|
||||||
|
SELECT a.account_code, a.account_name, ob.opening_debit, ob.opening_credit
|
||||||
|
FROM opening_balances ob
|
||||||
|
JOIN accounts a ON ob.account_id = a.account_id
|
||||||
|
WHERE ob.fiscal_year = 2025 AND a.account_code = '602'
|
||||||
|
""")
|
||||||
|
row = cur.fetchone()
|
||||||
|
if row:
|
||||||
|
print(f"\n【確認】")
|
||||||
|
print(f"{row['account_code']} {row['account_name']}: 貸方 {row['opening_credit']:,}円")
|
||||||
6
backend/sql/add_petty_cash.sql
Normal file
6
backend/sql/add_petty_cash.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-- =========================
|
||||||
|
-- 小口現金科目の追加
|
||||||
|
-- =========================
|
||||||
|
|
||||||
|
INSERT INTO accounts (account_code, account_name, account_type)
|
||||||
|
VALUES ('105', '小口現金', 'asset');
|
||||||
@@ -43,9 +43,10 @@ INSERT INTO opening_balances (fiscal_year, account_id, opening_debit, opening_cr
|
|||||||
(2025, (SELECT account_id FROM accounts WHERE account_code = '506'), 0, 669316),
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '506'), 0, 669316),
|
||||||
|
|
||||||
-- 資本金(601): 6,000,000(貸方)
|
-- 資本金(601): 6,000,000(貸方)
|
||||||
(2025, (SELECT account_id FROM accounts WHERE account_code = '601'), 0, 6000000);
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '601'), 0, 6000000),
|
||||||
|
|
||||||
-- 繰越利益剰余金(602): 5,206,146 は自動計算されるため入力不要
|
-- 繰越利益剰余金(602): 5,206,146(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '602'), 0, 5206146);
|
||||||
|
|
||||||
-- 確認用クエリ
|
-- 確認用クエリ
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
45
backend/update_opening_balances.py
Normal file
45
backend/update_opening_balances.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
os.environ['DB_HOST'] = '192.168.0.61'
|
||||||
|
os.environ['DB_PORT'] = '55432'
|
||||||
|
os.environ['DB_NAME'] = 'njts_acct'
|
||||||
|
os.environ['DB_USER'] = 'njts_app'
|
||||||
|
os.environ['DB_PASSWORD'] = 'njts_app2025'
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
# SQLファイルを読み込んで実行
|
||||||
|
with open('sql/insert_opening_balances_2025.sql', 'r', encoding='utf-8') as f:
|
||||||
|
sql_content = f.read()
|
||||||
|
|
||||||
|
# セミコロンで分割して実行
|
||||||
|
statements = [s.strip() for s in sql_content.split(';') if s.strip() and not s.strip().startswith('--')]
|
||||||
|
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
for statement in statements:
|
||||||
|
if statement.strip():
|
||||||
|
try:
|
||||||
|
cur.execute(statement)
|
||||||
|
print(f"✓ 実行成功")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ エラー: {e}")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
print("\n前期残高データを更新しました。")
|
||||||
|
|
||||||
|
# 確認
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
ob.opening_debit,
|
||||||
|
ob.opening_credit
|
||||||
|
FROM opening_balances ob
|
||||||
|
JOIN accounts a ON ob.account_id = a.account_id
|
||||||
|
WHERE ob.fiscal_year = 2025
|
||||||
|
ORDER BY a.account_code
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("\n【2025年度 前期残高一覧】")
|
||||||
|
for row in cur.fetchall():
|
||||||
|
print(f"{row['account_code']} {row['account_name']:20s} 借方:{row['opening_debit']:>12,} 貸方:{row['opening_credit']:>12,}")
|
||||||
@@ -45,6 +45,72 @@
|
|||||||
.tax-row {
|
.tax-row {
|
||||||
background: #f0f8ff;
|
background: #f0f8ff;
|
||||||
}
|
}
|
||||||
|
.print-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.page-break {
|
||||||
|
page-break-before: always;
|
||||||
|
}
|
||||||
|
.page-number {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
margin: 20mm;
|
||||||
|
}
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
#printArea,
|
||||||
|
#printArea * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
#printArea {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
button,
|
||||||
|
.no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.print-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
.print-header-left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.print-header-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.page-number {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
#printArea table {
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
#printArea th,
|
||||||
|
#printArea td {
|
||||||
|
padding: 3px 4px;
|
||||||
|
}
|
||||||
|
#printArea h3 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -217,21 +283,15 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
||||||
|
<button
|
||||||
|
onclick="printSearchResults()"
|
||||||
|
style="margin-left: 8px; color: blue"
|
||||||
|
>
|
||||||
|
🖨️ 印刷
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table>
|
<div id="printArea"></div>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>日付</th>
|
|
||||||
<th>摘要</th>
|
|
||||||
<th>借方合計</th>
|
|
||||||
<th>貸方合計</th>
|
|
||||||
<th>税率</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="searchResultList"></tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let rowSeq = 0; // 各行唯一ID
|
let rowSeq = 0; // 各行唯一ID
|
||||||
@@ -253,16 +313,26 @@
|
|||||||
|
|
||||||
const ACCOUNT_GROUPS = [
|
const ACCOUNT_GROUPS = [
|
||||||
{
|
{
|
||||||
label: "💰 資金",
|
label: "💰 現金・預金",
|
||||||
codes: ["101", "102", "103", "104"],
|
codes: ["101", "102", "103", "104", "105"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "📦 流動資産",
|
label: "📦 流動資産",
|
||||||
codes: ["201", "202", "203", "204", "205"],
|
codes: ["201", "202", "203", "204", "205", "210"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "🏗 固定資産",
|
label: "🏗️ 固定資産",
|
||||||
codes: ["401", "402", "403", "404", "405", "406", "407", "408"],
|
codes: [
|
||||||
|
"301",
|
||||||
|
"401",
|
||||||
|
"402",
|
||||||
|
"403",
|
||||||
|
"404",
|
||||||
|
"405",
|
||||||
|
"406",
|
||||||
|
"407",
|
||||||
|
"408",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "💳 負債",
|
label: "💳 負債",
|
||||||
@@ -276,16 +346,30 @@
|
|||||||
"507",
|
"507",
|
||||||
"508",
|
"508",
|
||||||
"509",
|
"509",
|
||||||
"210",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "🏢 資本",
|
label: "🏢 純資産",
|
||||||
codes: ["601", "602"],
|
codes: ["601", "602"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "📈 収益",
|
label: "📈 収益",
|
||||||
codes: ["701"],
|
codes: ["701", "702", "703", "704", "705"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "📉 費用",
|
||||||
|
codes: [
|
||||||
|
"801",
|
||||||
|
"802",
|
||||||
|
"803",
|
||||||
|
"804",
|
||||||
|
"805",
|
||||||
|
"806",
|
||||||
|
"807",
|
||||||
|
"808",
|
||||||
|
"809",
|
||||||
|
"810",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -410,9 +494,9 @@
|
|||||||
history = history.filter((d) => d !== description);
|
history = history.filter((d) => d !== description);
|
||||||
history.unshift(description);
|
history.unshift(description);
|
||||||
|
|
||||||
// 最大20件まで保存
|
// 最大100件まで保存
|
||||||
if (history.length > 20) {
|
if (history.length > 100) {
|
||||||
history = history.slice(0, 20);
|
history = history.slice(0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem("descriptionHistory", JSON.stringify(history));
|
localStorage.setItem("descriptionHistory", JSON.stringify(history));
|
||||||
@@ -462,10 +546,10 @@
|
|||||||
${buildAccountOptions()}
|
${buildAccountOptions()}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="text" class="debit right" ${
|
<td><input type="text" class="debit right" inputmode="numeric" style="ime-mode: inactive;" ${
|
||||||
isTaxRow ? "readonly" : ""
|
isTaxRow ? "readonly" : ""
|
||||||
} oninput="formatNumberInput(this)"></td>
|
} oninput="formatNumberInput(this)"></td>
|
||||||
<td><input type="text" class="credit right" ${
|
<td><input type="text" class="credit right" inputmode="numeric" style="ime-mode: inactive;" ${
|
||||||
isTaxRow ? "readonly" : ""
|
isTaxRow ? "readonly" : ""
|
||||||
} oninput="formatNumberInput(this)"></td>
|
} oninput="formatNumberInput(this)"></td>
|
||||||
<td>
|
<td>
|
||||||
@@ -504,6 +588,7 @@
|
|||||||
const recalc = () => handleTax(tr);
|
const recalc = () => handleTax(tr);
|
||||||
const debitInput = tr.querySelector(".debit");
|
const debitInput = tr.querySelector(".debit");
|
||||||
const creditInput = tr.querySelector(".credit");
|
const creditInput = tr.querySelector(".credit");
|
||||||
|
const accountSelect = tr.querySelector(".account");
|
||||||
|
|
||||||
debitInput.addEventListener("input", recalc);
|
debitInput.addEventListener("input", recalc);
|
||||||
creditInput.addEventListener("input", recalc);
|
creditInput.addEventListener("input", recalc);
|
||||||
@@ -512,6 +597,11 @@
|
|||||||
debitInput.addEventListener("blur", updateTotals);
|
debitInput.addEventListener("blur", updateTotals);
|
||||||
creditInput.addEventListener("blur", updateTotals);
|
creditInput.addEventListener("blur", updateTotals);
|
||||||
|
|
||||||
|
// 科目選択時に履歴を保存
|
||||||
|
accountSelect.addEventListener("change", () => {
|
||||||
|
saveAccountToRecent(parseInt(accountSelect.value));
|
||||||
|
});
|
||||||
|
|
||||||
tr.querySelector(".taxType").addEventListener("change", recalc);
|
tr.querySelector(".taxType").addEventListener("change", recalc);
|
||||||
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
||||||
}
|
}
|
||||||
@@ -957,6 +1047,23 @@
|
|||||||
function buildAccountOptions() {
|
function buildAccountOptions() {
|
||||||
let html = "";
|
let html = "";
|
||||||
|
|
||||||
|
// 最近使った科目を表示
|
||||||
|
const recentAccountIds = JSON.parse(
|
||||||
|
localStorage.getItem("recentAccounts") || "[]"
|
||||||
|
);
|
||||||
|
if (recentAccountIds.length > 0) {
|
||||||
|
html += `<optgroup label="⭐ 最近使った科目">`;
|
||||||
|
recentAccountIds.forEach((accountId) => {
|
||||||
|
const account = accounts.find((a) => a.account_id === accountId);
|
||||||
|
if (account) {
|
||||||
|
html += `<option value="${account.account_id}">
|
||||||
|
${account.account_code} ${account.account_name}
|
||||||
|
</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
html += `</optgroup>`;
|
||||||
|
}
|
||||||
|
|
||||||
// 已分组的科目
|
// 已分组的科目
|
||||||
ACCOUNT_GROUPS.forEach((group) => {
|
ACCOUNT_GROUPS.forEach((group) => {
|
||||||
html += `<optgroup label="${group.label}">`;
|
html += `<optgroup label="${group.label}">`;
|
||||||
@@ -989,9 +1096,189 @@
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 科目選択履歴を保存
|
||||||
|
function saveAccountToRecent(accountId) {
|
||||||
|
if (!accountId) return;
|
||||||
|
|
||||||
|
let recentAccounts = JSON.parse(
|
||||||
|
localStorage.getItem("recentAccounts") || "[]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 重複を除去して先頭に追加
|
||||||
|
recentAccounts = recentAccounts.filter((id) => id !== accountId);
|
||||||
|
recentAccounts.unshift(accountId);
|
||||||
|
|
||||||
|
// 最大20件まで保持
|
||||||
|
if (recentAccounts.length > 20) {
|
||||||
|
recentAccounts = recentAccounts.slice(0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("recentAccounts", JSON.stringify(recentAccounts));
|
||||||
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 仕訳検索機能(journal-list.htmlから統合)
|
// 仕訳検索機能(journal-list.htmlから統合)
|
||||||
// ========================================
|
// ========================================
|
||||||
|
// 検索結果を印刷(検索を実行してから印刷)
|
||||||
|
async function printSearchResults() {
|
||||||
|
// まず検索を実行
|
||||||
|
const from = document.getElementById("searchFromDate").value;
|
||||||
|
const to = document.getElementById("searchToDate").value;
|
||||||
|
const key = document.getElementById("searchKeyword").value;
|
||||||
|
const accountId = document.getElementById("searchAccountId").value;
|
||||||
|
const includeHistory = document.getElementById(
|
||||||
|
"searchIncludeHistory"
|
||||||
|
).checked;
|
||||||
|
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (from) qs.append("from_date", from);
|
||||||
|
if (to) qs.append("to_date", to);
|
||||||
|
if (key) qs.append("keyword", key);
|
||||||
|
if (accountId) qs.append("account_id", accountId);
|
||||||
|
if (includeHistory) qs.append("include_history", "true");
|
||||||
|
|
||||||
|
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// 印刷用に昇順でソート
|
||||||
|
const printData = [...data].sort((a, b) => {
|
||||||
|
if (a.entry_date !== b.entry_date) {
|
||||||
|
return a.entry_date.localeCompare(b.entry_date);
|
||||||
|
}
|
||||||
|
return a.journal_entry_id - b.journal_entry_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const printArea = document.getElementById("printArea");
|
||||||
|
printArea.innerHTML = "";
|
||||||
|
|
||||||
|
// 月ごとにデータをグループ化
|
||||||
|
const monthGroups = {};
|
||||||
|
printData.forEach((e) => {
|
||||||
|
const entryMonth = e.entry_date.substring(0, 7);
|
||||||
|
if (!monthGroups[entryMonth]) {
|
||||||
|
monthGroups[entryMonth] = [];
|
||||||
|
}
|
||||||
|
monthGroups[entryMonth].push(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 検索条件を生成
|
||||||
|
const accountSelect = document.getElementById("searchAccountId");
|
||||||
|
const accountName =
|
||||||
|
accountSelect.options[accountSelect.selectedIndex]?.text || "";
|
||||||
|
let conditions = [];
|
||||||
|
if (from || to) {
|
||||||
|
conditions.push(`期間: ${from || ""} ~ ${to || ""}`);
|
||||||
|
}
|
||||||
|
if (key) {
|
||||||
|
conditions.push(`摘要: ${key}`);
|
||||||
|
}
|
||||||
|
if (accountName && accountName !== "-- すべて --") {
|
||||||
|
conditions.push(`科目: ${accountName}`);
|
||||||
|
}
|
||||||
|
if (includeHistory) {
|
||||||
|
conditions.push(`修正履歴を含む`);
|
||||||
|
}
|
||||||
|
const conditionsText =
|
||||||
|
conditions.length > 0 ? conditions.join(" | ") : "検索条件: すべて";
|
||||||
|
|
||||||
|
// 月ごとにテーブルを生成(印刷用)
|
||||||
|
Object.keys(monthGroups)
|
||||||
|
.sort()
|
||||||
|
.forEach((month, index) => {
|
||||||
|
const monthData = monthGroups[month];
|
||||||
|
const monthDisplay = new Date(month + "-01").toLocaleDateString(
|
||||||
|
"ja-JP",
|
||||||
|
{
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.createElement("div");
|
||||||
|
if (index > 0) {
|
||||||
|
section.className = "page-break";
|
||||||
|
}
|
||||||
|
|
||||||
|
section.innerHTML = `
|
||||||
|
<h3 style="text-align: center; margin-bottom: 8px;">仕訳検索結果</h3>
|
||||||
|
<div class="print-header">
|
||||||
|
<div class="print-header-left">${conditionsText}</div>
|
||||||
|
<div class="print-header-right">${monthDisplay}</div>
|
||||||
|
</div>
|
||||||
|
<table style="margin-bottom: 20px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>日付</th>
|
||||||
|
<th>摘要</th>
|
||||||
|
<th>借方合計</th>
|
||||||
|
<th>貸方合計</th>
|
||||||
|
<th>税率</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<div class="page-number">ページ ${index + 1}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const tbody = section.querySelector("tbody");
|
||||||
|
let monthDebitTotal = 0;
|
||||||
|
let monthCreditTotal = 0;
|
||||||
|
|
||||||
|
monthData.forEach((e) => {
|
||||||
|
monthDebitTotal += e.debit_total;
|
||||||
|
monthCreditTotal += e.credit_total;
|
||||||
|
|
||||||
|
const versionInfo =
|
||||||
|
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${e.entry_date}</td>
|
||||||
|
<td style="text-align:left">${e.description}${versionInfo}</td>
|
||||||
|
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||||
|
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||||
|
<td style="text-align:center">${
|
||||||
|
e.tax_rates ? e.tax_rates + "%" : ""
|
||||||
|
}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="viewJournal(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})">表示</button>
|
||||||
|
<button onclick="copyJournal(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})" style="color: green;">複製</button>
|
||||||
|
<button onclick="reverseAndEdit(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})">修正</button>
|
||||||
|
<button onclick="deleteJournalEntry(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})" style="color: red;">削除</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 月合計行を追加
|
||||||
|
const totalRow = document.createElement("tr");
|
||||||
|
totalRow.style.fontWeight = "bold";
|
||||||
|
totalRow.style.backgroundColor = "#f0f0f0";
|
||||||
|
totalRow.innerHTML = `
|
||||||
|
<td colspan="2" style="text-align:right">合計:</td>
|
||||||
|
<td style="text-align:right">${monthDebitTotal.toLocaleString()}</td>
|
||||||
|
<td style="text-align:right">${monthCreditTotal.toLocaleString()}</td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(totalRow);
|
||||||
|
|
||||||
|
printArea.appendChild(section);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 少し待ってから印刷(データの表示を待つ)
|
||||||
|
setTimeout(() => {
|
||||||
|
window.print();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
async function searchJournals() {
|
async function searchJournals() {
|
||||||
const from = document.getElementById("searchFromDate").value;
|
const from = document.getElementById("searchFromDate").value;
|
||||||
const to = document.getElementById("searchToDate").value;
|
const to = document.getElementById("searchToDate").value;
|
||||||
@@ -1023,15 +1310,95 @@
|
|||||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const tbody = document.getElementById("searchResultList");
|
const printArea = document.getElementById("printArea");
|
||||||
tbody.innerHTML = "";
|
printArea.innerHTML = "";
|
||||||
|
|
||||||
|
// 月ごとにデータをグループ化
|
||||||
|
const monthGroups = {};
|
||||||
data.forEach((e) => {
|
data.forEach((e) => {
|
||||||
const tr = document.createElement("tr");
|
const entryMonth = e.entry_date.substring(0, 7); // YYYY-MM
|
||||||
// 修正履歴がある場合はバージョン番号を表示
|
if (!monthGroups[entryMonth]) {
|
||||||
const versionInfo =
|
monthGroups[entryMonth] = [];
|
||||||
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
}
|
||||||
tr.innerHTML = `
|
monthGroups[entryMonth].push(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 検索条件を生成
|
||||||
|
const conditionsText = (() => {
|
||||||
|
let conditions = [];
|
||||||
|
if (from || to) {
|
||||||
|
conditions.push(`期間: ${from || ""} ~ ${to || ""}`);
|
||||||
|
}
|
||||||
|
if (key) {
|
||||||
|
conditions.push(`摘要: ${key}`);
|
||||||
|
}
|
||||||
|
const accountSelect = document.getElementById("searchAccountId");
|
||||||
|
const accountName =
|
||||||
|
accountSelect.options[accountSelect.selectedIndex]?.text || "";
|
||||||
|
if (accountName && accountName !== "-- すべて --") {
|
||||||
|
conditions.push(`科目: ${accountName}`);
|
||||||
|
}
|
||||||
|
if (includeHistory) {
|
||||||
|
conditions.push(`修正履歴を含む`);
|
||||||
|
}
|
||||||
|
return conditions.length > 0
|
||||||
|
? conditions.join(" | ")
|
||||||
|
: "検索条件: すべて";
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 月ごとにテーブルを生成(画面表示は降順)
|
||||||
|
Object.keys(monthGroups)
|
||||||
|
.sort((a, b) => b.localeCompare(a)) // 月を降順で表示
|
||||||
|
.forEach((month, index) => {
|
||||||
|
const monthData = monthGroups[month];
|
||||||
|
const monthDisplay = new Date(month + "-01").toLocaleDateString(
|
||||||
|
"ja-JP",
|
||||||
|
{
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.createElement("div");
|
||||||
|
if (index > 0) {
|
||||||
|
section.className = "page-break";
|
||||||
|
}
|
||||||
|
|
||||||
|
section.innerHTML = `
|
||||||
|
<h3 style="text-align: center; margin-bottom: 8px;">仕訳検索結果</h3>
|
||||||
|
<div class="print-header">
|
||||||
|
<div class="print-header-left">${conditionsText}</div>
|
||||||
|
<div class="print-header-right">${monthDisplay}</div>
|
||||||
|
</div>
|
||||||
|
<table style="margin-bottom: 20px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>日付</th>
|
||||||
|
<th>摘要</th>
|
||||||
|
<th>借方合計</th>
|
||||||
|
<th>貸方合計</th>
|
||||||
|
<th>税率</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<div class="page-number">ページ ${index + 1}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const tbody = section.querySelector("tbody");
|
||||||
|
let monthDebitTotal = 0;
|
||||||
|
let monthCreditTotal = 0;
|
||||||
|
|
||||||
|
// 月内のデータも降順で表示(画面表示用)
|
||||||
|
monthData.forEach((e) => {
|
||||||
|
monthDebitTotal += e.debit_total;
|
||||||
|
monthCreditTotal += e.credit_total;
|
||||||
|
|
||||||
|
const versionInfo =
|
||||||
|
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.innerHTML = `
|
||||||
<td>${e.entry_date}</td>
|
<td>${e.entry_date}</td>
|
||||||
<td style="text-align:left">${e.description}${versionInfo}</td>
|
<td style="text-align:left">${e.description}${versionInfo}</td>
|
||||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||||
@@ -1054,8 +1421,23 @@
|
|||||||
})" style="color: red;">削除</button>
|
})" style="color: red;">削除</button>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 月合計行を追加
|
||||||
|
const totalRow = document.createElement("tr");
|
||||||
|
totalRow.style.fontWeight = "bold";
|
||||||
|
totalRow.style.backgroundColor = "#f0f0f0";
|
||||||
|
totalRow.innerHTML = `
|
||||||
|
<td colspan="2" style="text-align:right">合計:</td>
|
||||||
|
<td style="text-align:right">${monthDebitTotal.toLocaleString()}</td>
|
||||||
|
<td style="text-align:right">${monthCreditTotal.toLocaleString()}</td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(totalRow);
|
||||||
|
|
||||||
|
printArea.appendChild(section);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewJournal(id) {
|
function viewJournal(id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user