修正
This commit is contained in:
@@ -8,7 +8,13 @@ SELECT
|
|||||||
FROM journal_lines l
|
FROM journal_lines l
|
||||||
JOIN journal_entries j
|
JOIN journal_entries j
|
||||||
ON j.journal_id = l.journal_id
|
ON j.journal_id = l.journal_id
|
||||||
|
LEFT JOIN journal_entries child
|
||||||
|
ON child.parent_entry_id = j.journal_id
|
||||||
WHERE l.account_id = %(account_id)s
|
WHERE l.account_id = %(account_id)s
|
||||||
AND j.journal_date BETWEEN %(date_from)s AND %(date_to)s
|
AND j.journal_date BETWEEN %(date_from)s AND %(date_to)s
|
||||||
|
-- 逆仕訳を除外
|
||||||
|
AND j.description NOT LIKE '[逆仕訳]%'
|
||||||
|
-- 子仕訳(修正後仕訳)を持つ修正前の仕訳を除外
|
||||||
|
AND child.journal_id IS NULL
|
||||||
ORDER BY j.journal_date, l.line_id;
|
ORDER BY j.journal_date, l.line_id;
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -64,8 +64,14 @@ def get_general_ledger(
|
|||||||
FROM journal_entries j
|
FROM journal_entries j
|
||||||
JOIN journal_lines l
|
JOIN journal_lines l
|
||||||
ON l.journal_id = j.journal_id
|
ON l.journal_id = j.journal_id
|
||||||
|
LEFT JOIN journal_entries child
|
||||||
|
ON child.parent_entry_id = j.journal_id
|
||||||
WHERE l.account_id = %s
|
WHERE l.account_id = %s
|
||||||
AND j.journal_date BETWEEN %s AND %s
|
AND j.journal_date BETWEEN %s AND %s
|
||||||
|
-- 逆仕訳を除外
|
||||||
|
AND j.description NOT LIKE '[逆仕訳]%'
|
||||||
|
-- 子仕訳(修正後仕訳)を持つ修正前の仕訳を除外
|
||||||
|
AND child.journal_id IS NULL
|
||||||
ORDER BY j.journal_date, l.line_id
|
ORDER BY j.journal_date, l.line_id
|
||||||
""", (account_id, date_from, date_to))
|
""", (account_id, date_from, date_to))
|
||||||
|
|
||||||
|
|||||||
@@ -60,14 +60,19 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0)
|
opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0)
|
||||||
|
|
||||||
# date_from より前の仕訳による増減
|
# date_from より前の仕訳による増減
|
||||||
|
# 修正された仕訳(親となった仕訳)は除外
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening
|
SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening
|
||||||
FROM journal_lines l
|
FROM journal_lines l
|
||||||
JOIN journal_entries j
|
JOIN journal_entries j
|
||||||
ON j.journal_entry_id = l.journal_entry_id
|
ON j.journal_entry_id = l.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child
|
||||||
|
ON child.parent_entry_id = j.journal_entry_id
|
||||||
WHERE l.account_id = %s
|
WHERE l.account_id = %s
|
||||||
AND j.entry_date < %s
|
AND j.entry_date < %s
|
||||||
AND j.is_deleted = false
|
AND j.is_deleted = false
|
||||||
|
AND j.description NOT LIKE '[逆仕訳]%%'
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
""", (aid, date_from))
|
""", (aid, date_from))
|
||||||
opening_from_journals = D(cur.fetchone()["opening"])
|
opening_from_journals = D(cur.fetchone()["opening"])
|
||||||
|
|
||||||
@@ -75,6 +80,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
opening = opening_balance_from_table + opening_from_journals
|
opening = opening_balance_from_table + opening_from_journals
|
||||||
|
|
||||||
# 当期増減(date_from ~ date_to)
|
# 当期増減(date_from ~ date_to)
|
||||||
|
# 修正された仕訳(親となった仕訳)は除外
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(SUM(l.debit), 0) AS debit,
|
COALESCE(SUM(l.debit), 0) AS debit,
|
||||||
@@ -82,9 +88,13 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
FROM journal_lines l
|
FROM journal_lines l
|
||||||
JOIN journal_entries j
|
JOIN journal_entries j
|
||||||
ON j.journal_entry_id = l.journal_entry_id
|
ON j.journal_entry_id = l.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child
|
||||||
|
ON child.parent_entry_id = j.journal_entry_id
|
||||||
WHERE l.account_id = %s
|
WHERE l.account_id = %s
|
||||||
AND j.entry_date BETWEEN %s AND %s
|
AND j.entry_date BETWEEN %s AND %s
|
||||||
AND j.is_deleted = false
|
AND j.is_deleted = false
|
||||||
|
AND j.description NOT LIKE '[逆仕訳]%%'
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
""", (aid, date_from, date_to))
|
""", (aid, date_from, date_to))
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
debit = D(row["debit"])
|
debit = D(row["debit"])
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ opening AS (
|
|||||||
SUM(jl.debit) AS opening_debit,
|
SUM(jl.debit) AS opening_debit,
|
||||||
SUM(jl.credit) AS opening_credit
|
SUM(jl.credit) AS opening_credit
|
||||||
FROM journal_entries je
|
FROM journal_entries je
|
||||||
JOIN journal_lines jl ON jl.journal_id = je.journal_id
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
WHERE je.description = '前期残高'
|
WHERE je.description = '前期残高'
|
||||||
AND je.journal_date = %(start_date)s
|
AND je.entry_date = %(start_date)s
|
||||||
|
AND je.is_deleted = false
|
||||||
GROUP BY jl.account_id
|
GROUP BY jl.account_id
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -29,9 +30,15 @@ period AS (
|
|||||||
SUM(jl.debit) AS period_debit,
|
SUM(jl.debit) AS period_debit,
|
||||||
SUM(jl.credit) AS period_credit
|
SUM(jl.credit) AS period_credit
|
||||||
FROM journal_entries je
|
FROM journal_entries je
|
||||||
JOIN journal_lines jl ON jl.journal_id = je.journal_id
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
WHERE je.journal_date BETWEEN %(start_date)s AND %(end_date)s
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.entry_date BETWEEN %(start_date)s AND %(end_date)s
|
||||||
AND je.description <> '前期残高'
|
AND je.description <> '前期残高'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
-- 逆仕訳を除外([逆仕訳]プレフィックスを持つ仕訳)
|
||||||
|
AND je.description NOT LIKE '[逆仕訳]%'
|
||||||
|
-- 子仕訳(修正後仕訳)を持つ修正前の仕訳を除外
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
GROUP BY jl.account_id
|
GROUP BY jl.account_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class JournalEntryRequest(BaseModel):
|
|||||||
tax_paid_account_id: Optional[int] = None # 仮払消費税等 勘定
|
tax_paid_account_id: Optional[int] = None # 仮払消費税等 勘定
|
||||||
tax_received_account_id: Optional[int] = None # 仮受消費税等 勘定
|
tax_received_account_id: Optional[int] = None # 仮受消費税等 勘定
|
||||||
rounding_mode: str = "round" # "round", "floor", or "ceil"
|
rounding_mode: str = "round" # "round", "floor", or "ceil"
|
||||||
|
parent_entry_id: Optional[int] = None # 修正元の仕訳ID
|
||||||
|
|
||||||
class JournalEntryUpdateRequest(BaseModel):
|
class JournalEntryUpdateRequest(BaseModel):
|
||||||
description: str
|
description: str
|
||||||
@@ -58,7 +59,8 @@ def get_journal_entries(
|
|||||||
je.fiscal_year,
|
je.fiscal_year,
|
||||||
je.version,
|
je.version,
|
||||||
COALESCE(SUM(jl.debit), 0) as debit_total,
|
COALESCE(SUM(jl.debit), 0) as debit_total,
|
||||||
COALESCE(SUM(jl.credit), 0) as credit_total
|
COALESCE(SUM(jl.credit), 0) as credit_total,
|
||||||
|
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
|
||||||
FROM journal_entries je
|
FROM journal_entries je
|
||||||
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
||||||
WHERE je.is_deleted = false
|
WHERE je.is_deleted = false
|
||||||
@@ -74,7 +76,8 @@ def get_journal_entries(
|
|||||||
je.fiscal_year,
|
je.fiscal_year,
|
||||||
je.version,
|
je.version,
|
||||||
COALESCE(SUM(jl.debit), 0) as debit_total,
|
COALESCE(SUM(jl.debit), 0) as debit_total,
|
||||||
COALESCE(SUM(jl.credit), 0) as credit_total
|
COALESCE(SUM(jl.credit), 0) as credit_total,
|
||||||
|
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
|
||||||
FROM journal_entries je
|
FROM journal_entries je
|
||||||
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
||||||
WHERE je.is_deleted = false
|
WHERE je.is_deleted = false
|
||||||
@@ -202,15 +205,17 @@ def create_reverse_entry(journal_id: int):
|
|||||||
fiscal_year,
|
fiscal_year,
|
||||||
version,
|
version,
|
||||||
is_deleted,
|
is_deleted,
|
||||||
created_by
|
created_by,
|
||||||
|
parent_entry_id
|
||||||
)
|
)
|
||||||
VALUES (%s, %s, %s, 1, false, %s)
|
VALUES (%s, %s, %s, 1, false, %s, %s)
|
||||||
RETURNING journal_entry_id
|
RETURNING journal_entry_id
|
||||||
""", (
|
""", (
|
||||||
header["entry_date"],
|
header["entry_date"],
|
||||||
f"[逆仕訳] {header['description']}",
|
f"[逆仕訳] {header['description']}",
|
||||||
header["fiscal_year"],
|
header["fiscal_year"],
|
||||||
"system"
|
"system",
|
||||||
|
journal_id # 逆仕訳の元となる仕訳ID
|
||||||
))
|
))
|
||||||
|
|
||||||
new_entry_id = cur.fetchone()["journal_entry_id"]
|
new_entry_id = cur.fetchone()["journal_entry_id"]
|
||||||
@@ -322,15 +327,17 @@ def create_journal_entry(req: JournalEntryRequest):
|
|||||||
fiscal_year,
|
fiscal_year,
|
||||||
version,
|
version,
|
||||||
is_deleted,
|
is_deleted,
|
||||||
created_by
|
created_by,
|
||||||
|
parent_entry_id
|
||||||
)
|
)
|
||||||
VALUES (%s, %s, %s, 1, false, %s)
|
VALUES (%s, %s, %s, 1, false, %s, %s)
|
||||||
RETURNING journal_entry_id
|
RETURNING journal_entry_id
|
||||||
""", (
|
""", (
|
||||||
req.entry_date,
|
req.entry_date,
|
||||||
req.description,
|
req.description,
|
||||||
fiscal_year,
|
fiscal_year,
|
||||||
"system"
|
"system",
|
||||||
|
req.parent_entry_id
|
||||||
))
|
))
|
||||||
|
|
||||||
entry_id = cur.fetchone()["journal_entry_id"]
|
entry_id = cur.fetchone()["journal_entry_id"]
|
||||||
|
|||||||
109
backend/check_data.py
Normal file
109
backend/check_data.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 逸速ビル関連の仕訳を確認
|
||||||
|
cur.execute("""
|
||||||
|
SELECT journal_entry_id, entry_date, description, parent_entry_id, is_deleted
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE description LIKE '%遠達ビル%'
|
||||||
|
ORDER BY journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print("\n遠達ビル関連の仕訳:")
|
||||||
|
for r in rows:
|
||||||
|
print(f"ID: {r['journal_entry_id']}, Date: {r['entry_date']}, Desc: {r['description']}, Parent: {r['parent_entry_id']}, is_deleted: {r['is_deleted']}")
|
||||||
|
|
||||||
|
# parent_entry_idが設定されている仕訳IDの一覧を取得
|
||||||
|
cur.execute("""
|
||||||
|
SELECT DISTINCT parent_entry_id
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE parent_entry_id IS NOT NULL
|
||||||
|
ORDER BY parent_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
parent_ids = [r['parent_entry_id'] for r in cur.fetchall()]
|
||||||
|
print(f"\nparent_entry_idとして参照されているID: {parent_ids}")
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# 実際の検索クエリと同じロジックでテスト
|
||||||
|
print("\n--- 検索クエリのテスト(include_history=False) ---")
|
||||||
|
conn2 = get_connection()
|
||||||
|
cur2 = conn2.cursor()
|
||||||
|
|
||||||
|
# まず、条件なしでID 80が取得できるか確認
|
||||||
|
cur2.execute("""
|
||||||
|
SELECT journal_entry_id, description
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE journal_entry_id = 80
|
||||||
|
""")
|
||||||
|
print(f"ID 80の存在確認: {cur2.fetchall()}")
|
||||||
|
|
||||||
|
# 各条件を個別にチェック
|
||||||
|
cur2.execute("SELECT journal_entry_id FROM journal_entries WHERE journal_entry_id = 80 AND is_deleted = false")
|
||||||
|
print(f"is_deleted=false: {len(cur2.fetchall())}件")
|
||||||
|
|
||||||
|
cur2.execute("SELECT journal_entry_id FROM journal_entries WHERE journal_entry_id = 80 AND description NOT LIKE '%[逆仕訳]%'")
|
||||||
|
print(f"NOT LIKE '%%[逆仕訳]%%': {len(cur2.fetchall())}件")
|
||||||
|
|
||||||
|
cur2.execute("""
|
||||||
|
SELECT journal_entry_id FROM journal_entries
|
||||||
|
WHERE journal_entry_id = 80
|
||||||
|
AND journal_entry_id NOT IN (SELECT parent_entry_id FROM journal_entries WHERE parent_entry_id IS NOT NULL)
|
||||||
|
""")
|
||||||
|
print(f"NOT IN parent_entry_id: {len(cur2.fetchall())}件")
|
||||||
|
|
||||||
|
cur2.execute("SELECT journal_entry_id FROM journal_entries WHERE journal_entry_id = 80 AND description LIKE '%逸速ビル%'")
|
||||||
|
print(f"LIKE '%%逸速ビル%%': {len(cur2.fetchall())}件")
|
||||||
|
|
||||||
|
cur2.execute("SELECT journal_entry_id FROM journal_entries WHERE journal_entry_id = 80 AND description LIKE '%遠達ビル%'")
|
||||||
|
print(f"LIKE '%%遠達ビル%%': {len(cur2.fetchall())}件")
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
je.fiscal_year,
|
||||||
|
je.version,
|
||||||
|
COALESCE(SUM(jl.debit), 0) as debit_total,
|
||||||
|
COALESCE(SUM(jl.credit), 0) as credit_total,
|
||||||
|
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ' ORDER BY CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END) as tax_rates
|
||||||
|
FROM journal_entries je
|
||||||
|
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
||||||
|
WHERE je.is_deleted = false
|
||||||
|
AND je.description NOT LIKE %s
|
||||||
|
AND je.journal_entry_id NOT IN (
|
||||||
|
SELECT parent_entry_id
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE parent_entry_id IS NOT NULL
|
||||||
|
)
|
||||||
|
AND je.entry_date >= %s AND je.entry_date <= %s
|
||||||
|
AND je.description LIKE %s
|
||||||
|
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year, je.version
|
||||||
|
ORDER BY je.entry_date DESC, je.journal_entry_id DESC, je.version DESC
|
||||||
|
"""
|
||||||
|
|
||||||
|
cur2.execute(sql, ['%[逆仕訳]%', '2025-06-01', '2025-06-30', '%逸速ビル%'])
|
||||||
|
results = cur2.fetchall()
|
||||||
|
|
||||||
|
print(f"\n検索結果({len(results)}件):")
|
||||||
|
for r in results:
|
||||||
|
print(f"ID: {r['journal_entry_id']}, Date: {r['entry_date']}, Desc: {r['description']}, Tax: {r['tax_rates']}")
|
||||||
|
|
||||||
|
cur2.close()
|
||||||
|
conn2.close()
|
||||||
71
backend/check_data_debug.py
Normal file
71
backend/check_data_debug.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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:
|
||||||
|
print("=== 6月の普通預金(102)仕訳 ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
je.parent_entry_id,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
JOIN accounts a ON a.account_id = jl.account_id
|
||||||
|
WHERE je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND a.account_code = '102'
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(f"ID: {row['journal_entry_id']:4d}, Parent: {str(row['parent_entry_id']):4s}, "
|
||||||
|
f"Debit: {row['debit']:6.0f}, Credit: {row['credit']:6.0f}, "
|
||||||
|
f"Desc: {row['description'][:50]}")
|
||||||
|
|
||||||
|
print("\n=== 試算表クエリでの除外状況 ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.description,
|
||||||
|
child.journal_entry_id as has_child,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit,
|
||||||
|
CASE
|
||||||
|
WHEN je.description LIKE '[逆仕訳]%%' THEN '除外:逆仕訳'
|
||||||
|
WHEN child.journal_entry_id IS NOT NULL THEN '除外:親仕訳'
|
||||||
|
ELSE '計上'
|
||||||
|
END as status
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
JOIN accounts a ON a.account_id = jl.account_id
|
||||||
|
WHERE je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND a.account_code = '102'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
total_debit = 0
|
||||||
|
total_credit = 0
|
||||||
|
for row in rows:
|
||||||
|
status = row['status']
|
||||||
|
if status == '計上':
|
||||||
|
total_debit += float(row['debit'])
|
||||||
|
total_credit += float(row['credit'])
|
||||||
|
print(f"ID: {row['journal_entry_id']:4d}, Status: {status:12s}, "
|
||||||
|
f"Debit: {row['debit']:6.0f}, Credit: {row['credit']:6.0f}, "
|
||||||
|
f"Desc: {row['description'][:40]}")
|
||||||
|
|
||||||
|
print(f"\n計上される借方合計: {total_debit:.0f}")
|
||||||
|
print(f"計上される貸方合計: {total_credit:.0f}")
|
||||||
45
backend/check_parent_ids.py
Normal file
45
backend/check_parent_ids.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
with get_connection() as conn, conn.cursor() as cur:
|
||||||
|
# 普通預金(102)の6月の仕訳を確認
|
||||||
|
cur.execute('''
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
je.parent_entry_id,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit,
|
||||||
|
CASE WHEN child.journal_entry_id IS NOT NULL THEN 'Y' ELSE 'N' END as has_child
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = (SELECT account_id FROM accounts WHERE account_code = '102')
|
||||||
|
AND je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
''')
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
print('ID\t日付\t\t説明\t\t\t\t\tparent\t借方\t\t貸方\t\tchild?')
|
||||||
|
print('='*120)
|
||||||
|
for r in rows:
|
||||||
|
desc = r['description'][:40].ljust(40)
|
||||||
|
parent = str(r['parent_entry_id']) if r['parent_entry_id'] else '-'
|
||||||
|
print(f"{r['journal_entry_id']}\t{r['entry_date']}\t{desc}\t{parent}\t{r['debit']}\t{r['credit']}\t{r['has_child']}")
|
||||||
|
|
||||||
|
print('\n集計(child=Nのみ):')
|
||||||
|
cur.execute('''
|
||||||
|
SELECT
|
||||||
|
SUM(jl.debit) as total_debit,
|
||||||
|
SUM(jl.credit) as total_credit
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = (SELECT account_id FROM accounts WHERE account_code = '102')
|
||||||
|
AND je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
|
''')
|
||||||
|
result = cur.fetchone()
|
||||||
|
print(f"借方: {result['total_debit']}, 貸方: {result['total_credit']}")
|
||||||
93
backend/check_trial_balance.py
Normal file
93
backend/check_trial_balance.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 社会保険料関連の仕訳を確認
|
||||||
|
print("\n=== 2025年4月 社会保険料の仕訳チェーン ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
je.parent_entry_id,
|
||||||
|
je.is_deleted,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (SELECT 1 FROM journal_entries child WHERE child.parent_entry_id = je.journal_entry_id)
|
||||||
|
THEN 'Y' ELSE 'N'
|
||||||
|
END as has_child
|
||||||
|
FROM journal_entries je
|
||||||
|
WHERE je.description LIKE '%2025年4月 社会保険料%'
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for r in rows:
|
||||||
|
print(f"ID:{r['journal_entry_id']:3d} 親:{r['parent_entry_id'] or '-':>3} 子:{r['has_child']} 削除:{r['is_deleted']} {r['description']}")
|
||||||
|
|
||||||
|
# 試算表と同じロジックで集計
|
||||||
|
print("\n=== 試算表と同じロジックでの集計(2025-06-01~2025-06-30) ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit,
|
||||||
|
je.parent_entry_id,
|
||||||
|
je.is_deleted,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (SELECT 1 FROM journal_entries child WHERE child.parent_entry_id = je.journal_entry_id)
|
||||||
|
THEN 'Y' ELSE 'N'
|
||||||
|
END as has_child
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
WHERE a.account_code = '102'
|
||||||
|
AND je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND je.description <> '前期残高'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
print(f"\n{'ID':>3} {'日付':10} {'借方':>12} {'貸方':>12} {'親':>3} {'子':2} {'集計':4} 摘要")
|
||||||
|
print("-" * 120)
|
||||||
|
|
||||||
|
total_debit_all = 0
|
||||||
|
total_credit_all = 0
|
||||||
|
total_debit_filtered = 0
|
||||||
|
total_credit_filtered = 0
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for r in rows:
|
||||||
|
total_debit_all += r['debit']
|
||||||
|
total_credit_all += r['credit']
|
||||||
|
|
||||||
|
# 試算表のロジック: 子を持たない仕訳のみ集計
|
||||||
|
should_include = r['has_child'] == 'N'
|
||||||
|
|
||||||
|
if should_include:
|
||||||
|
total_debit_filtered += r['debit']
|
||||||
|
total_credit_filtered += r['credit']
|
||||||
|
|
||||||
|
status = "集計" if should_include else "除外"
|
||||||
|
parent_str = str(r['parent_entry_id']) if r['parent_entry_id'] else "-"
|
||||||
|
|
||||||
|
print(f"{r['journal_entry_id']:3d} {r['entry_date']} {r['debit']:>12,.0f} {r['credit']:>12,.0f} {parent_str:>3} {r['has_child']:2} {status:4} {r['description'][:60]}")
|
||||||
|
|
||||||
|
print("-" * 120)
|
||||||
|
print(f"全仕訳: 借方={total_debit_all:>12,.0f} 貸方={total_credit_all:>12,.0f}")
|
||||||
|
print(f"試算表集計: 借方={total_debit_filtered:>12,.0f} 貸方={total_credit_filtered:>12,.0f}")
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
25
backend/sql/fix_existing_data.sql
Normal file
25
backend/sql/fix_existing_data.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-- 既存の修正仕訳データを修正
|
||||||
|
|
||||||
|
-- 1. ID 80のis_deletedをfalseに設定(修正版は削除されていないはず)
|
||||||
|
UPDATE journal_entries
|
||||||
|
SET is_deleted = false
|
||||||
|
WHERE journal_entry_id = 80;
|
||||||
|
|
||||||
|
-- 2. 最新の修正仕訳(【修正後】【修正後】など)のparent_entry_idが正しく設定されているか確認
|
||||||
|
-- まず確認クエリ
|
||||||
|
SELECT journal_entry_id, description, parent_entry_id, is_deleted
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE description LIKE '%遠達ビル%'
|
||||||
|
ORDER BY journal_entry_id;
|
||||||
|
|
||||||
|
-- 3. 修正チェーンを正しく設定
|
||||||
|
-- ID 98: [逆仕訳] 【修正後】·遠達ビル 事務所費用 → parent_entry_idを80に設定
|
||||||
|
UPDATE journal_entries SET parent_entry_id = 80 WHERE journal_entry_id = 98;
|
||||||
|
|
||||||
|
-- ID 99: 【修正後】【修正後】·遠達ビル 事務所費用 → parent_entry_idを98に設定
|
||||||
|
UPDATE journal_entries SET parent_entry_id = 98 WHERE journal_entry_id = 99;
|
||||||
|
|
||||||
|
-- 4. すべての【修正後】仕訳のis_deletedをfalseに設定
|
||||||
|
UPDATE journal_entries
|
||||||
|
SET is_deleted = false
|
||||||
|
WHERE description LIKE '【修正後】%';
|
||||||
78
backend/sql/set_all_parent_entry_ids.sql
Normal file
78
backend/sql/set_all_parent_entry_ids.sql
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
-- 既存の全仕訳のparent_entry_idを一括設定
|
||||||
|
|
||||||
|
-- ステップ1: 現在の状況を確認
|
||||||
|
SELECT
|
||||||
|
journal_entry_id,
|
||||||
|
entry_date,
|
||||||
|
description,
|
||||||
|
parent_entry_id,
|
||||||
|
is_deleted
|
||||||
|
FROM journal_entries
|
||||||
|
ORDER BY entry_date, journal_entry_id;
|
||||||
|
|
||||||
|
-- ステップ2: [逆仕訳]のparent_entry_idを設定
|
||||||
|
-- [逆仕訳]の直前の同じ日付・同じベース説明の仕訳を親とする
|
||||||
|
UPDATE journal_entries je_reverse
|
||||||
|
SET parent_entry_id = (
|
||||||
|
SELECT je_original.journal_entry_id
|
||||||
|
FROM journal_entries je_original
|
||||||
|
WHERE je_original.entry_date = je_reverse.entry_date
|
||||||
|
AND je_original.journal_entry_id < je_reverse.journal_entry_id
|
||||||
|
AND REPLACE(je_reverse.description, '[逆仕訳] ', '') = je_original.description
|
||||||
|
AND je_original.description NOT LIKE '[逆仕訳]%'
|
||||||
|
ORDER BY je_original.journal_entry_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE je_reverse.description LIKE '[逆仕訳]%'
|
||||||
|
AND je_reverse.parent_entry_id IS NULL;
|
||||||
|
|
||||||
|
-- ステップ3: 【修正後】のparent_entry_idを設定
|
||||||
|
-- 【修正後】の直前の逆仕訳を親とする
|
||||||
|
UPDATE journal_entries je_modified
|
||||||
|
SET parent_entry_id = (
|
||||||
|
SELECT je_reverse.journal_entry_id
|
||||||
|
FROM journal_entries je_reverse
|
||||||
|
WHERE je_reverse.journal_entry_id < je_modified.journal_entry_id
|
||||||
|
AND je_reverse.description = '[逆仕訳] ' || REPLACE(je_modified.description, '【修正後】', '')
|
||||||
|
ORDER BY je_reverse.journal_entry_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE je_modified.description LIKE '【修正後】%'
|
||||||
|
AND je_modified.parent_entry_id IS NULL;
|
||||||
|
|
||||||
|
-- ステップ4: 複数の【修正後】プレフィックスがある場合の処理
|
||||||
|
-- 【修正後】【修正後】などのケース
|
||||||
|
UPDATE journal_entries je_modified
|
||||||
|
SET parent_entry_id = (
|
||||||
|
SELECT je_reverse.journal_entry_id
|
||||||
|
FROM journal_entries je_reverse
|
||||||
|
WHERE je_reverse.entry_date = je_modified.entry_date
|
||||||
|
AND je_reverse.journal_entry_id < je_modified.journal_entry_id
|
||||||
|
AND je_reverse.description LIKE '[逆仕訳]%'
|
||||||
|
ORDER BY je_reverse.journal_entry_id DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
WHERE je_modified.description LIKE '【修正後】%'
|
||||||
|
AND je_modified.parent_entry_id IS NULL;
|
||||||
|
|
||||||
|
-- ステップ5: 結果確認
|
||||||
|
SELECT
|
||||||
|
journal_entry_id,
|
||||||
|
entry_date,
|
||||||
|
LEFT(description, 50) as description,
|
||||||
|
parent_entry_id,
|
||||||
|
is_deleted
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE parent_entry_id IS NOT NULL
|
||||||
|
ORDER BY entry_date, journal_entry_id;
|
||||||
|
|
||||||
|
-- ステップ6: parent_entry_idが設定されていない逆仕訳・修正後を確認
|
||||||
|
SELECT
|
||||||
|
journal_entry_id,
|
||||||
|
entry_date,
|
||||||
|
LEFT(description, 50) as description,
|
||||||
|
parent_entry_id
|
||||||
|
FROM journal_entries
|
||||||
|
WHERE (description LIKE '[逆仕訳]%' OR description LIKE '【修正後】%')
|
||||||
|
AND parent_entry_id IS NULL
|
||||||
|
ORDER BY journal_entry_id;
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
-- 修正後の仕訳に対して、元の仕訳IDをparent_entry_idに設定
|
-- 修正後の仕訳に対して、元の仕訳IDをparent_entry_idに設定
|
||||||
|
|
||||||
|
-- 特定の修正チェーンを手動で設定(72 → 79 → 80)
|
||||||
|
-- UPDATE journal_entries SET parent_entry_id = 72 WHERE journal_entry_id = 79;
|
||||||
|
-- UPDATE journal_entries SET parent_entry_id = 79 WHERE journal_entry_id = 80;
|
||||||
|
|
||||||
-- ステップ1: 【修正後】の仕訳を確認
|
-- ステップ1: 【修正後】の仕訳を確認
|
||||||
SELECT
|
SELECT
|
||||||
je_modified.journal_entry_id as modified_id,
|
je_modified.journal_entry_id as modified_id,
|
||||||
|
|||||||
150
backend/verify_trial_balance.py
Normal file
150
backend/verify_trial_balance.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("\n" + "="*120)
|
||||||
|
print("試算表ロジック検証 - 普通預金(102) 2025年6月")
|
||||||
|
print("="*120)
|
||||||
|
|
||||||
|
# 試算表SQLと同じロジックで集計
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
jl.account_id,
|
||||||
|
SUM(jl.debit) as total_debit,
|
||||||
|
SUM(jl.credit) as total_credit
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
WHERE je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND je.description <> '前期残高'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
|
AND a.account_code = '102'
|
||||||
|
GROUP BY jl.account_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = cur.fetchone()
|
||||||
|
print(f"\n【試算表の集計結果】")
|
||||||
|
print(f"借方発生額: {result['total_debit']:>15,.0f} 円")
|
||||||
|
print(f"貸方発生額: {result['total_credit']:>15,.0f} 円")
|
||||||
|
|
||||||
|
# 詳細な内訳を確認
|
||||||
|
print(f"\n{'='*120}")
|
||||||
|
print("【集計対象の仕訳詳細】")
|
||||||
|
print(f"{'='*120}")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit,
|
||||||
|
je.parent_entry_id,
|
||||||
|
CASE WHEN child.journal_entry_id IS NOT NULL THEN 'Y' ELSE 'N' END as has_child
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
WHERE je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
AND je.description <> '前期残高'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND a.account_code = '102'
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
debit_sum = 0
|
||||||
|
credit_sum = 0
|
||||||
|
debit_included = 0
|
||||||
|
credit_included = 0
|
||||||
|
|
||||||
|
print(f"\n{'ID':>3} {'日付':10} {'借方':>13} {'貸方':>13} {'親ID':>5} {'子':3} {'集計':4} 摘要")
|
||||||
|
print("-" * 120)
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
is_included = r['has_child'] == 'N'
|
||||||
|
status = "○" if is_included else "×"
|
||||||
|
|
||||||
|
debit_sum += r['debit']
|
||||||
|
credit_sum += r['credit']
|
||||||
|
|
||||||
|
if is_included:
|
||||||
|
debit_included += r['debit']
|
||||||
|
credit_included += r['credit']
|
||||||
|
|
||||||
|
parent_str = str(r['parent_entry_id']) if r['parent_entry_id'] else "-"
|
||||||
|
|
||||||
|
# 逆仕訳・修正後をマーク
|
||||||
|
desc = r['description'][:60]
|
||||||
|
if '[逆仕訳]' in desc:
|
||||||
|
desc = f"🔄 {desc}"
|
||||||
|
elif '【修正後】' in desc:
|
||||||
|
desc = f"✏️ {desc}"
|
||||||
|
|
||||||
|
print(f"{r['journal_entry_id']:3d} {r['entry_date']} {r['debit']:>13,.0f} {r['credit']:>13,.0f} {parent_str:>5} {r['has_child']:3} {status:4} {desc}")
|
||||||
|
|
||||||
|
print("-" * 120)
|
||||||
|
print(f"{'全仕訳合計':62} {debit_sum:>13,.0f} {credit_sum:>13,.0f}")
|
||||||
|
print(f"{'集計対象(子なし)':60} {debit_included:>13,.0f} {credit_included:>13,.0f}")
|
||||||
|
|
||||||
|
# parent_entry_idの問題を確認
|
||||||
|
print(f"\n{'='*120}")
|
||||||
|
print("【parent_entry_idチェーン確認】")
|
||||||
|
print(f"{'='*120}")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.journal_entry_id as id,
|
||||||
|
je.description,
|
||||||
|
je.parent_entry_id as parent,
|
||||||
|
(SELECT description FROM journal_entries WHERE journal_entry_id = je.parent_entry_id) as parent_desc,
|
||||||
|
(SELECT COUNT(*) FROM journal_entries child WHERE child.parent_entry_id = je.journal_entry_id) as child_count
|
||||||
|
FROM journal_entries je
|
||||||
|
WHERE (je.description LIKE '%社会保険%' OR je.description LIKE '%遠達ビル%')
|
||||||
|
AND je.entry_date BETWEEN '2025-06-01' AND '2025-06-30'
|
||||||
|
ORDER BY je.journal_entry_id
|
||||||
|
""")
|
||||||
|
|
||||||
|
print(f"\n{'ID':>3} {'親ID':>5} {'子数':>4} 摘要 → 親の摘要")
|
||||||
|
print("-" * 120)
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for r in rows:
|
||||||
|
parent_str = str(r['parent']) if r['parent'] else "-"
|
||||||
|
parent_desc_str = r['parent_desc'][:40] if r['parent_desc'] else "-"
|
||||||
|
print(f"{r['id']:3d} {parent_str:>5} {r['child_count']:>4} {r['description'][:50]}")
|
||||||
|
if r['parent_desc']:
|
||||||
|
print(f" └→ 親: {parent_desc_str}")
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print(f"\n{'='*120}")
|
||||||
|
print("【結論】")
|
||||||
|
print(f"{'='*120}")
|
||||||
|
print("""
|
||||||
|
正しい集計方法:
|
||||||
|
1. parent_entry_idチェーンが正しく設定されている場合
|
||||||
|
→ 最後の修正版のみが「子を持たない」状態になり、それのみが集計される
|
||||||
|
|
||||||
|
2. parent_entry_idの設定ミスがある場合
|
||||||
|
→ 逆仕訳や途中の修正版も「子を持たない」と判定され、重複集計される
|
||||||
|
|
||||||
|
対策:
|
||||||
|
- すべてのparent_entry_idを正しく設定する
|
||||||
|
- チェーン: 元 → 逆仕訳 → 修正後 → 逆仕訳 → 修正後 → ...
|
||||||
|
""")
|
||||||
166
docs/trial_balance_fix.md
Normal file
166
docs/trial_balance_fix.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# 試算表修正履歴除外対応
|
||||||
|
|
||||||
|
## 問題概要
|
||||||
|
|
||||||
|
従来、仕訳修正時に以下の流れで処理していました:
|
||||||
|
|
||||||
|
1. 原仕訳を保留
|
||||||
|
2. 逆仕訳を生成(`[逆仕訳]` プレフィックス付き)
|
||||||
|
3. 修正後仕訳を生成(`【修正後】` プレフィックス付き)
|
||||||
|
|
||||||
|
しかし、試算表計算時に全ての仕訳を無条件に統計していたため:
|
||||||
|
|
||||||
|
- 原仕訳
|
||||||
|
- 逆仕訳
|
||||||
|
- 修正後仕訳
|
||||||
|
|
||||||
|
の 3 つが全て計上され、同一取引が複数回カウントされる問題が発生していました。
|
||||||
|
|
||||||
|
### 具体的な症状
|
||||||
|
|
||||||
|
- 銀行科目(普通預金)で借方発生額・貸方発生額が同額計上される
|
||||||
|
- 期末残高が銀行通帳と一致しない
|
||||||
|
- 試算表数値が実態と乖離
|
||||||
|
|
||||||
|
## 解決方針
|
||||||
|
|
||||||
|
**方案 B:逆仕訳方式 + 試算表排除ロジック** を採用
|
||||||
|
|
||||||
|
以下の理由により、試算表 SQL で履歴データを除外する方式を選択:
|
||||||
|
|
||||||
|
1. 既存データに逆仕訳・修正後仕訳の関係性が構築済み
|
||||||
|
2. 修正履歴を保持できる
|
||||||
|
3. 最小限のコード変更で対応可能
|
||||||
|
4. データマイグレーション不要
|
||||||
|
|
||||||
|
## 実装内容
|
||||||
|
|
||||||
|
### 1. 試算表 SQL 修正
|
||||||
|
|
||||||
|
**ファイル**: `backend/app/modules/trial_balance/sql.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
period AS (
|
||||||
|
SELECT
|
||||||
|
jl.account_id,
|
||||||
|
SUM(jl.debit) AS period_debit,
|
||||||
|
SUM(jl.credit) AS period_credit
|
||||||
|
FROM journal_entries je
|
||||||
|
JOIN journal_lines jl ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.entry_date BETWEEN %(start_date)s AND %(end_date)s
|
||||||
|
AND je.description <> '前期残高'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
-- ★ 逆仕訳を除外
|
||||||
|
AND je.description NOT LIKE '[逆仕訳]%'
|
||||||
|
-- ★ 修正前仕訳を除外(parent_entry_idを参照する子仕訳がある場合)
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
|
GROUP BY jl.account_id
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 総勘定元帳 SQL 修正
|
||||||
|
|
||||||
|
**ファイル**: `backend/app/modules/general_ledger/sql.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
SELECT
|
||||||
|
j.journal_date,
|
||||||
|
j.description,
|
||||||
|
l.debit,
|
||||||
|
l.credit
|
||||||
|
FROM journal_lines l
|
||||||
|
JOIN journal_entries j
|
||||||
|
ON j.journal_id = l.journal_id
|
||||||
|
LEFT JOIN journal_entries child
|
||||||
|
ON child.parent_entry_id = j.journal_id
|
||||||
|
WHERE l.account_id = %(account_id)s
|
||||||
|
AND j.journal_date BETWEEN %(date_from)s AND %(date_to)s
|
||||||
|
-- ★ 逆仕訳を除外
|
||||||
|
AND j.description NOT LIKE '[逆仕訳]%'
|
||||||
|
-- ★ 修正前仕訳を除外
|
||||||
|
AND child.journal_id IS NULL
|
||||||
|
ORDER BY j.journal_date, l.line_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 元帳 SQL 修正
|
||||||
|
|
||||||
|
**ファイル**: `backend/app/modules/ledger/router.py`
|
||||||
|
|
||||||
|
同様に逆仕訳と修正前仕訳を除外する条件を追加。
|
||||||
|
|
||||||
|
## 除外ロジックの説明
|
||||||
|
|
||||||
|
### 除外対象 1:逆仕訳
|
||||||
|
|
||||||
|
```sql
|
||||||
|
AND je.description NOT LIKE '[逆仕訳]%'
|
||||||
|
```
|
||||||
|
|
||||||
|
- 説明に `[逆仕訳]` プレフィックスを持つ仕訳を除外
|
||||||
|
- これらは元仕訳を打ち消すためのもので、試算表には計上しない
|
||||||
|
|
||||||
|
### 除外対象 2:修正前仕訳
|
||||||
|
|
||||||
|
```sql
|
||||||
|
LEFT JOIN journal_entries child ON child.parent_entry_id = je.journal_entry_id
|
||||||
|
...
|
||||||
|
AND child.journal_entry_id IS NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
- `parent_entry_id` を参照する子仕訳(修正後仕訳)が存在する場合、その親仕訳は除外
|
||||||
|
- 修正された仕訳は最新版のみを計上
|
||||||
|
|
||||||
|
## 仕訳修正の流れ(参考)
|
||||||
|
|
||||||
|
1. **元仕訳**: `journal_entry_id = 100`, `parent_entry_id = NULL`
|
||||||
|
2. **逆仕訳**: `journal_entry_id = 101`, `parent_entry_id = 100`, `description = "[逆仕訳] xxx"`
|
||||||
|
3. **修正後仕訳**: `journal_entry_id = 102`, `parent_entry_id = 101`, `description = "【修正後】xxx"`
|
||||||
|
|
||||||
|
試算表では:
|
||||||
|
|
||||||
|
- 元仕訳(100):除外(子仕訳 101 が存在)
|
||||||
|
- 逆仕訳(101):除外(`[逆仕訳]` プレフィックス)
|
||||||
|
- 修正後仕訳(102):**計上**(最新版)
|
||||||
|
|
||||||
|
## 会計原則の遵守
|
||||||
|
|
||||||
|
✅ 試算表は「当前有効的仕訳」のみを統計
|
||||||
|
|
||||||
|
✅ 同一業務が同一期間で 1 回のみ計上
|
||||||
|
|
||||||
|
✅ 履歴データ(逆仕訳・修正前仕訳)は除外
|
||||||
|
|
||||||
|
✅ 試算表結果が銀行通帳・実態と一致
|
||||||
|
|
||||||
|
## 影響範囲
|
||||||
|
|
||||||
|
### 修正対象
|
||||||
|
|
||||||
|
- ✅ 試算表(Trial Balance)
|
||||||
|
- ✅ 総勘定元帳(General Ledger)
|
||||||
|
- ✅ 元帳(Ledger)
|
||||||
|
|
||||||
|
### 影響なし
|
||||||
|
|
||||||
|
- 仕訳一覧表示(修正履歴表示機能は既存のまま)
|
||||||
|
- 仕訳登録・修正ロジック
|
||||||
|
- データベーススキーマ
|
||||||
|
|
||||||
|
## テスト項目
|
||||||
|
|
||||||
|
1. [ ] 修正前の仕訳がある状態で試算表を表示
|
||||||
|
2. [ ] 逆仕訳が正しく除外されることを確認
|
||||||
|
3. [ ] 修正後仕訳のみが計上されることを確認
|
||||||
|
4. [ ] 普通預金の期末残高が通帳と一致することを確認
|
||||||
|
5. [ ] 総勘定元帳でも同様に履歴が除外されることを確認
|
||||||
|
|
||||||
|
## 今後の拡張性
|
||||||
|
|
||||||
|
現在は `description` の文字列マッチで逆仕訳を判定していますが、将来的には以下の改善も検討可能:
|
||||||
|
|
||||||
|
- `is_reversal` フラグの追加
|
||||||
|
- `entry_type` カラム(`original`, `reversal`, `revised`)の追加
|
||||||
|
- より明示的な状態管理
|
||||||
|
|
||||||
|
ただし、現在の実装でも要件は満たしており、早急な変更は不要です。
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<h2>修正仕訳入力</h2>
|
<h2>修正仕訳入力</h2>
|
||||||
|
|
||||||
<label>仕訳日</label>
|
<label>仕訳日</label>
|
||||||
<input type="date" id="entryDate" />
|
<input type="date" id="entryDate" min="1900-01-01" max="9999-12-31" />
|
||||||
|
|
||||||
<label>摘要</label>
|
<label>摘要</label>
|
||||||
<input type="text" id="description" style="width: 60%" />
|
<input type="text" id="description" style="width: 60%" />
|
||||||
@@ -143,6 +143,12 @@
|
|||||||
tr.querySelectorAll("input")[0].value = line.debit || "";
|
tr.querySelectorAll("input")[0].value = line.debit || "";
|
||||||
tr.querySelectorAll("input")[1].value = line.credit || "";
|
tr.querySelectorAll("input")[1].value = line.credit || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 入力時に合計を自動更新
|
||||||
|
tr.querySelectorAll("input").forEach((input) => {
|
||||||
|
input.addEventListener("input", recalc);
|
||||||
|
input.addEventListener("change", recalc);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRow(btn) {
|
function removeRow(btn) {
|
||||||
@@ -197,6 +203,12 @@
|
|||||||
if (lines.length < 2) return showError("仕訳行は2行以上必要です");
|
if (lines.length < 2) return showError("仕訳行は2行以上必要です");
|
||||||
if (d !== c) return showError("借方合計と貸方合計が一致していません");
|
if (d !== c) return showError("借方合計と貸方合計が一致していません");
|
||||||
|
|
||||||
|
// 逆仕訳IDを親IDとして取得(逆仕訳が存在する場合)
|
||||||
|
const src = localStorage.getItem("editSource");
|
||||||
|
const srcData = src ? JSON.parse(src) : null;
|
||||||
|
// 【修正後】仕訳のparent_entry_idは、直前に作成された逆仕訳のID
|
||||||
|
const parentEntryId = srcData?.reversed_journal_entry_id || null;
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
entry_date: entryDate,
|
entry_date: entryDate,
|
||||||
description: desc,
|
description: desc,
|
||||||
@@ -204,6 +216,7 @@
|
|||||||
tax_paid_account_id: null,
|
tax_paid_account_id: null,
|
||||||
tax_received_account_id: null,
|
tax_received_account_id: null,
|
||||||
rounding_mode: "floor",
|
rounding_mode: "floor",
|
||||||
|
parent_entry_id: parentEntryId,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -103,6 +103,33 @@
|
|||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
gap: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<label>借方合計:</label>
|
||||||
|
<span id="totalDebit" style="color: #0066cc; font-size: 16px">0</span>
|
||||||
|
円
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>貸方合計:</label>
|
||||||
|
<span id="totalCredit" style="color: #cc0000; font-size: 16px">0</span>
|
||||||
|
円
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="balanceStatus"
|
||||||
|
style="margin-left: auto; padding: 4px 12px; border-radius: 4px"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px; display: flex; gap: 8px">
|
<div style="margin-top: 10px; display: flex; gap: 8px">
|
||||||
<button onclick="addRow()">+ 行追加</button>
|
<button onclick="addRow()">+ 行追加</button>
|
||||||
<button onclick="submitJournal()">登録</button>
|
<button onclick="submitJournal()">登録</button>
|
||||||
@@ -199,6 +226,7 @@
|
|||||||
<th>摘要</th>
|
<th>摘要</th>
|
||||||
<th>借方合計</th>
|
<th>借方合計</th>
|
||||||
<th>貸方合計</th>
|
<th>貸方合計</th>
|
||||||
|
<th>税率</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -301,6 +329,29 @@
|
|||||||
|
|
||||||
// 摘要履歴を読み込み
|
// 摘要履歴を読み込み
|
||||||
loadDescriptionHistory();
|
loadDescriptionHistory();
|
||||||
|
|
||||||
|
// 各フィールドの変更時に合計を更新
|
||||||
|
document
|
||||||
|
.getElementById("entryDate")
|
||||||
|
.addEventListener("change", updateTotals);
|
||||||
|
document
|
||||||
|
.getElementById("description")
|
||||||
|
.addEventListener("input", updateTotals);
|
||||||
|
document
|
||||||
|
.getElementById("taxPaidSelect")
|
||||||
|
.addEventListener("change", updateTotals);
|
||||||
|
document
|
||||||
|
.getElementById("taxReceivedSelect")
|
||||||
|
.addEventListener("change", updateTotals);
|
||||||
|
document
|
||||||
|
.getElementById("roundingMode")
|
||||||
|
.addEventListener("change", () => {
|
||||||
|
// 端数処理変更時は全ての税行を再計算
|
||||||
|
const rows = document.querySelectorAll(
|
||||||
|
"#linesTable tbody tr:not(.tax-row)"
|
||||||
|
);
|
||||||
|
rows.forEach((row) => handleTax(row));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@@ -451,8 +502,16 @@
|
|||||||
// 普通行:绑定事件
|
// 普通行:绑定事件
|
||||||
if (!isTaxRow) {
|
if (!isTaxRow) {
|
||||||
const recalc = () => handleTax(tr);
|
const recalc = () => handleTax(tr);
|
||||||
tr.querySelector(".debit").addEventListener("input", recalc);
|
const debitInput = tr.querySelector(".debit");
|
||||||
tr.querySelector(".credit").addEventListener("input", recalc);
|
const creditInput = tr.querySelector(".credit");
|
||||||
|
|
||||||
|
debitInput.addEventListener("input", recalc);
|
||||||
|
creditInput.addEventListener("input", recalc);
|
||||||
|
|
||||||
|
// blur(フォーカス離脱)時に合計を更新
|
||||||
|
debitInput.addEventListener("blur", updateTotals);
|
||||||
|
creditInput.addEventListener("blur", updateTotals);
|
||||||
|
|
||||||
tr.querySelector(".taxType").addEventListener("change", recalc);
|
tr.querySelector(".taxType").addEventListener("change", recalc);
|
||||||
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
||||||
}
|
}
|
||||||
@@ -473,6 +532,49 @@
|
|||||||
const row = btn.closest("tr");
|
const row = btn.closest("tr");
|
||||||
removeTaxRowsOf(row);
|
removeTaxRowsOf(row);
|
||||||
row.remove();
|
row.remove();
|
||||||
|
updateTotals(); // 削除後に合計を更新
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// 借方・貸方合計を計算して表示
|
||||||
|
// -----------------------------
|
||||||
|
function updateTotals() {
|
||||||
|
let totalDebit = 0;
|
||||||
|
let totalCredit = 0;
|
||||||
|
|
||||||
|
const rows = document.querySelectorAll("#linesTable tbody tr");
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const debitValue =
|
||||||
|
row.querySelector(".debit")?.value.replace(/,/g, "") || "0";
|
||||||
|
const creditValue =
|
||||||
|
row.querySelector(".credit")?.value.replace(/,/g, "") || "0";
|
||||||
|
|
||||||
|
totalDebit += Number(debitValue) || 0;
|
||||||
|
totalCredit += Number(creditValue) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表示を更新
|
||||||
|
document.getElementById("totalDebit").textContent =
|
||||||
|
totalDebit.toLocaleString();
|
||||||
|
document.getElementById("totalCredit").textContent =
|
||||||
|
totalCredit.toLocaleString();
|
||||||
|
|
||||||
|
// 貸借一致チェック
|
||||||
|
const balanceStatus = document.getElementById("balanceStatus");
|
||||||
|
if (totalDebit === totalCredit && totalDebit > 0) {
|
||||||
|
balanceStatus.textContent = "✓ 貸借一致";
|
||||||
|
balanceStatus.style.background = "#d4edda";
|
||||||
|
balanceStatus.style.color = "#155724";
|
||||||
|
} else if (totalDebit === 0 && totalCredit === 0) {
|
||||||
|
balanceStatus.textContent = "";
|
||||||
|
balanceStatus.style.background = "";
|
||||||
|
} else {
|
||||||
|
balanceStatus.textContent = `✗ 差額: ${Math.abs(
|
||||||
|
totalDebit - totalCredit
|
||||||
|
).toLocaleString()}`;
|
||||||
|
balanceStatus.style.background = "#f8d7da";
|
||||||
|
balanceStatus.style.color = "#721c24";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -491,10 +593,16 @@
|
|||||||
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
if (taxType === "none" || taxDir === "none") return;
|
if (taxType === "none" || taxDir === "none") {
|
||||||
|
updateTotals(); // 税なしでも合計更新
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const totalAmount = debit || credit;
|
const totalAmount = debit || credit;
|
||||||
if (!totalAmount) return;
|
if (!totalAmount) {
|
||||||
|
updateTotals();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rate = taxType === "8" ? 0.08 : 0.1;
|
const rate = taxType === "8" ? 0.08 : 0.1;
|
||||||
const roundingMode = document.getElementById("roundingMode").value;
|
const roundingMode = document.getElementById("roundingMode").value;
|
||||||
@@ -517,7 +625,10 @@
|
|||||||
|
|
||||||
if (taxDir === "paid") {
|
if (taxDir === "paid") {
|
||||||
const taxAcc = document.getElementById("taxPaidSelect").value;
|
const taxAcc = document.getElementById("taxPaidSelect").value;
|
||||||
if (!taxAcc) return alert("仮払消費税等 勘定を選択してください");
|
if (!taxAcc) {
|
||||||
|
updateTotals();
|
||||||
|
return alert("仮払消費税等 勘定を選択してください");
|
||||||
|
}
|
||||||
addRow(
|
addRow(
|
||||||
true,
|
true,
|
||||||
{ accountId: taxAcc, amount: taxAmount, side: "debit" },
|
{ accountId: taxAcc, amount: taxAmount, side: "debit" },
|
||||||
@@ -526,13 +637,18 @@
|
|||||||
}
|
}
|
||||||
if (taxDir === "received") {
|
if (taxDir === "received") {
|
||||||
const taxAcc = document.getElementById("taxReceivedSelect").value;
|
const taxAcc = document.getElementById("taxReceivedSelect").value;
|
||||||
if (!taxAcc) return alert("仮受消費税等 勘定を選択してください");
|
if (!taxAcc) {
|
||||||
|
updateTotals();
|
||||||
|
return alert("仮受消費税等 勘定を選択してください");
|
||||||
|
}
|
||||||
addRow(
|
addRow(
|
||||||
true,
|
true,
|
||||||
{ accountId: taxAcc, amount: taxAmount, side: "credit" },
|
{ accountId: taxAcc, amount: taxAmount, side: "credit" },
|
||||||
parentId
|
parentId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTotals(); // 税行追加後に合計更新
|
||||||
}
|
}
|
||||||
|
|
||||||
// 税行は常に「直後の1行」を使う
|
// 税行は常に「直後の1行」を使う
|
||||||
@@ -686,6 +802,12 @@
|
|||||||
rowSeq = 0;
|
rowSeq = 0;
|
||||||
addRow();
|
addRow();
|
||||||
|
|
||||||
|
// 借方合計・貸方合計を0にリセット
|
||||||
|
document.getElementById("totalDebit").textContent = "0";
|
||||||
|
document.getElementById("totalCredit").textContent = "0";
|
||||||
|
document.getElementById("balanceStatus").textContent = "";
|
||||||
|
document.getElementById("balanceStatus").style.background = "";
|
||||||
|
|
||||||
// 仕訳検索を自動実行して最新の登録状況を表示
|
// 仕訳検索を自動実行して最新の登録状況を表示
|
||||||
searchJournals();
|
searchJournals();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -911,9 +1033,12 @@
|
|||||||
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${e.entry_date}</td>
|
<td>${e.entry_date}</td>
|
||||||
<td>${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>
|
||||||
<td style="text-align:right">${e.credit_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>
|
<td>
|
||||||
<button onclick="viewJournal(${
|
<button onclick="viewJournal(${
|
||||||
e.journal_entry_id
|
e.journal_entry_id
|
||||||
@@ -1050,6 +1175,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const srcData = await srcRes.json();
|
const srcData = await srcRes.json();
|
||||||
|
// 逆仕訳IDを追加して保存(【修正後】仕訳のparent_entry_idとして使用)
|
||||||
|
srcData.reversed_journal_entry_id = data.reversed_journal_entry_id;
|
||||||
localStorage.setItem("editSource", JSON.stringify(srcData));
|
localStorage.setItem("editSource", JSON.stringify(srcData));
|
||||||
|
|
||||||
// 修正画面を開く
|
// 修正画面を開く
|
||||||
|
|||||||
@@ -138,6 +138,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const srcData = await srcRes.json();
|
const srcData = await srcRes.json();
|
||||||
|
// 逆仕訳IDを追加して保存(【修正後】仕訳のparent_entry_idとして使用)
|
||||||
|
srcData.reversed_journal_entry_id = data.reversed_journal_entry_id;
|
||||||
localStorage.setItem("editSource", JSON.stringify(srcData));
|
localStorage.setItem("editSource", JSON.stringify(srcData));
|
||||||
|
|
||||||
// 修正画面を開く
|
// 修正画面を開く
|
||||||
|
|||||||
@@ -99,10 +99,10 @@
|
|||||||
|
|
||||||
<div class="search-form">
|
<div class="search-form">
|
||||||
<label for="dateFrom">開始年月日:</label>
|
<label for="dateFrom">開始年月日:</label>
|
||||||
<input type="date" id="dateFrom" />
|
<input type="date" id="dateFrom" min="1900-01-01" max="9999-12-31" />
|
||||||
|
|
||||||
<label for="dateTo">終了年月日:</label>
|
<label for="dateTo">終了年月日:</label>
|
||||||
<input type="date" id="dateTo" />
|
<input type="date" id="dateTo" min="1900-01-01" max="9999-12-31" />
|
||||||
|
|
||||||
<button id="btnSearch">検索</button>
|
<button id="btnSearch">検索</button>
|
||||||
<button id="btnFullYear" class="secondary">全年度表示</button>
|
<button id="btnFullYear" class="secondary">全年度表示</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user