修正
This commit is contained in:
185
analyze_accountant_diff.py
Normal file
185
analyze_accountant_diff.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
"""
|
||||||
|
12月2025年の仕訳データを詳しく確認し、売上高の差異原因を特定する
|
||||||
|
"""
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
|
conn = psycopg2.connect(
|
||||||
|
host='192.168.0.61',
|
||||||
|
port=55432,
|
||||||
|
dbname='njts_acct',
|
||||||
|
user='njts_app',
|
||||||
|
password='njts_app2025'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print("【分析1】月別売上高の集計 (2025年6月〜12月)")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
DATE_TRUNC('month', je.entry_date)::date AS month,
|
||||||
|
COALESCE(SUM(jl.credit), 0) - COALESCE(SUM(jl.debit), 0) AS revenue
|
||||||
|
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 je.is_latest = true
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01'
|
||||||
|
AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_code = '701'
|
||||||
|
GROUP BY DATE_TRUNC('month', je.entry_date)
|
||||||
|
ORDER BY month
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
total_sys = 0
|
||||||
|
for row in rows:
|
||||||
|
print(f" {row[0].strftime('%Y年%m月')}: {row[1]:>12,.0f} 円")
|
||||||
|
total_sys += row[1]
|
||||||
|
print(f" {'合計':>10}: {total_sys:>12,.0f} 円")
|
||||||
|
print(f"\n 税理士の売上高合計: 28,896,368 円")
|
||||||
|
print(f" 差額: {total_sys - 28896368:>12,.0f} 円")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【分析2】2025年12月の全仕訳明細")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.entry_date,
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.description,
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit
|
||||||
|
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 je.is_latest = true
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-12-01'
|
||||||
|
AND je.entry_date <= '2025-12-31'
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id, jl.journal_line_id
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
print(f"{'日付':<12} {'ID':<6} {'摘要':<30} {'科目CD':<8} {'科目名':<15} {'借方':>12} {'貸方':>12}")
|
||||||
|
print("-" * 100)
|
||||||
|
for row in rows:
|
||||||
|
print(f"{str(row[0]):<12} {row[1]:<6} {str(row[2])[:28]:<30} {str(row[3]):<8} {str(row[4]):<15} {row[5] if row[5] else 0:>12,.0f} {row[6] if row[6] else 0:>12,.0f}")
|
||||||
|
else:
|
||||||
|
print("2025年12月の仕訳データが見つかりません!")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【分析3】売上高差異の内訳 (3,432,724円の差異原因)")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
# Monthly revenue breakdown
|
||||||
|
print("\n月別売上高(システム vs 税理士推定):")
|
||||||
|
print(" ※税理士の月別内訳は不明なため、12月分の存在確認のみ実施")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【分析4】2025年12月の売上関連仕訳のみ")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
je.entry_date,
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.description,
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit
|
||||||
|
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 je.is_latest = true
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-12-01'
|
||||||
|
AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_code IN ('701', '702', '201') -- 売上高、受取利息、売掛金
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
dec_revenue = 0
|
||||||
|
if rows:
|
||||||
|
print(f"{'日付':<12} {'ID':<6} {'摘要':<30} {'科目名':<15} {'借方':>12} {'貸方':>12}")
|
||||||
|
print("-" * 90)
|
||||||
|
for row in rows:
|
||||||
|
credit = row[6] if row[6] else 0
|
||||||
|
debit = row[5] if row[5] else 0
|
||||||
|
if row[4] == '売上高':
|
||||||
|
dec_revenue += credit - debit
|
||||||
|
print(f"{str(row[0]):<12} {row[1]:<6} {str(row[2])[:28]:<30} {str(row[4]):<15} {debit:>12,.0f} {credit:>12,.0f}")
|
||||||
|
print(f"\n 2025年12月の売上高: {dec_revenue:>12,.0f} 円")
|
||||||
|
else:
|
||||||
|
print("2025年12月の売上関連仕訳なし")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【分析5】費用項目の差異詳細")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
system_data = {
|
||||||
|
"役員報酬": 2520000,
|
||||||
|
"給料手当": 7520000,
|
||||||
|
"法定福利費": 1439958,
|
||||||
|
"福利厚生費": 243760,
|
||||||
|
"外注費": 7490000,
|
||||||
|
"交際費": 806883,
|
||||||
|
"会議費": 440642,
|
||||||
|
"旅費交通費": 131763,
|
||||||
|
"通信費": 32063,
|
||||||
|
"消耗品費": 1061190,
|
||||||
|
"地代家賃": 1128184,
|
||||||
|
"支払手数料": 19470,
|
||||||
|
"支払報酬料": 165000,
|
||||||
|
"保険料": 33950,
|
||||||
|
"租税公課": -161590,
|
||||||
|
}
|
||||||
|
|
||||||
|
accountant_data = {
|
||||||
|
"役員報酬": 2520000,
|
||||||
|
"給料手当": 2700000,
|
||||||
|
"賞与": 4300000,
|
||||||
|
"法定福利費": 1588604,
|
||||||
|
"福利厚生費": 242978,
|
||||||
|
"外注費": 7490000,
|
||||||
|
"交際費": 915007,
|
||||||
|
"会議費": 219070,
|
||||||
|
"旅費交通費": 522149,
|
||||||
|
"通信費": 31459,
|
||||||
|
"消耗品費": 1027007,
|
||||||
|
"修繕費": 986,
|
||||||
|
"新聞図書費": 9900,
|
||||||
|
"支払手数料": 168800,
|
||||||
|
"地代家賃": 1081822,
|
||||||
|
"保険料": 33950,
|
||||||
|
"租税公課": 33100,
|
||||||
|
"法人税住民税事業税": 747,
|
||||||
|
}
|
||||||
|
|
||||||
|
all_keys = sorted(set(list(system_data.keys()) + list(accountant_data.keys())))
|
||||||
|
print(f"{'費用科目':<15} {'税理士':>12} {'システム':>12} {'差異(+は自社多)':>15} 備考")
|
||||||
|
print("-" * 65)
|
||||||
|
total_diff = 0
|
||||||
|
for k in all_keys:
|
||||||
|
acc = accountant_data.get(k, 0)
|
||||||
|
sys = system_data.get(k, 0)
|
||||||
|
diff = sys - acc
|
||||||
|
total_diff += diff
|
||||||
|
note = ""
|
||||||
|
if diff != 0:
|
||||||
|
note = "← 要確認" if abs(diff) > 100000 else ""
|
||||||
|
print(f"{k:<15} {acc:>12,.0f} {sys:>12,.0f} {diff:>+15,.0f} {note}")
|
||||||
|
|
||||||
|
print(f"{'合計':<15} {sum(accountant_data.values()):>12,.0f} {sum(system_data.values()):>12,.0f} {total_diff:>+15,.0f}")
|
||||||
|
print(f"\n※費用差異: システムの費用が {total_diff:+,.0f} 円 (負=税理士の費用が多い)")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
@@ -131,6 +131,40 @@ def approve_bonus(bonus_id: int, approved_by: str):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{bonus_id}", response_model=schemas.BonusPayment)
|
||||||
|
def update_bonus(bonus_id: int, request: schemas.BonusUpdateRequest):
|
||||||
|
"""賞与の支給項目を更新(再計算は別途実行)"""
|
||||||
|
with get_connection() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT status FROM bonus_payments WHERE bonus_id = %s", (bonus_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
raise HTTPException(status_code=404, detail="賞与が見つかりません")
|
||||||
|
if row["status"] == "approved":
|
||||||
|
raise HTTPException(status_code=400, detail="承認済みの賞与は編集できません")
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
UPDATE bonus_payments SET
|
||||||
|
base_bonus = %s, performance_bonus = %s, other_bonus = %s,
|
||||||
|
other_deduction = %s,
|
||||||
|
payment_date = COALESCE(%s, payment_date),
|
||||||
|
bonus_type = COALESCE(%s, bonus_type)
|
||||||
|
WHERE bonus_id = %s
|
||||||
|
RETURNING *
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
request.base_bonus, request.performance_bonus, request.other_bonus,
|
||||||
|
request.other_deduction,
|
||||||
|
request.payment_date, request.bonus_type,
|
||||||
|
bonus_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{bonus_id}/recalculate", response_model=schemas.BonusPayment)
|
@router.post("/{bonus_id}/recalculate", response_model=schemas.BonusPayment)
|
||||||
def recalculate_bonus(bonus_id: int):
|
def recalculate_bonus(bonus_id: int):
|
||||||
"""賞与を再計算"""
|
"""賞与を再計算"""
|
||||||
@@ -170,7 +204,8 @@ def recalculate_bonus(bonus_id: int):
|
|||||||
base_bonus = %s, performance_bonus = %s, other_bonus = %s,
|
base_bonus = %s, performance_bonus = %s, other_bonus = %s,
|
||||||
total_bonus = %s,
|
total_bonus = %s,
|
||||||
health_insurance = %s, care_insurance = %s, pension_insurance = %s,
|
health_insurance = %s, care_insurance = %s, pension_insurance = %s,
|
||||||
employment_insurance = %s, income_tax = %s, other_deduction = %s,
|
employment_insurance = %s, child_support = %s,
|
||||||
|
income_tax = %s, other_deduction = %s,
|
||||||
total_deduction = %s, net_bonus = %s,
|
total_deduction = %s, net_bonus = %s,
|
||||||
calculated_at = %s, calculated_by = %s
|
calculated_at = %s, calculated_by = %s
|
||||||
WHERE bonus_id = %s
|
WHERE bonus_id = %s
|
||||||
@@ -181,6 +216,7 @@ def recalculate_bonus(bonus_id: int):
|
|||||||
calc_result["other_bonus"], calc_result["total_bonus"],
|
calc_result["other_bonus"], calc_result["total_bonus"],
|
||||||
calc_result["health_insurance"], calc_result["care_insurance"],
|
calc_result["health_insurance"], calc_result["care_insurance"],
|
||||||
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
calc_result["pension_insurance"], calc_result["employment_insurance"],
|
||||||
|
calc_result["child_support"],
|
||||||
calc_result["income_tax"], calc_result["other_deduction"],
|
calc_result["income_tax"], calc_result["other_deduction"],
|
||||||
calc_result["total_deduction"], calc_result["net_bonus"],
|
calc_result["total_deduction"], calc_result["net_bonus"],
|
||||||
datetime.now(), "recalculated",
|
datetime.now(), "recalculated",
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ class BonusCalculationRequest(BaseModel):
|
|||||||
calculated_by: str = "system"
|
calculated_by: str = "system"
|
||||||
|
|
||||||
|
|
||||||
|
class BonusUpdateRequest(BaseModel):
|
||||||
|
"""賞与支給項目の更新リクエスト"""
|
||||||
|
base_bonus: Decimal
|
||||||
|
performance_bonus: Decimal = Decimal("0")
|
||||||
|
other_bonus: Decimal = Decimal("0")
|
||||||
|
other_deduction: Decimal = Decimal("0")
|
||||||
|
payment_date: Optional[date] = None
|
||||||
|
bonus_type: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class BonusPayment(BonusPaymentBase):
|
class BonusPayment(BonusPaymentBase):
|
||||||
"""賞与計算結果"""
|
"""賞与計算結果"""
|
||||||
bonus_id: int
|
bonus_id: int
|
||||||
@@ -47,6 +57,7 @@ class BonusPayment(BonusPaymentBase):
|
|||||||
care_insurance: Decimal
|
care_insurance: Decimal
|
||||||
pension_insurance: Decimal
|
pension_insurance: Decimal
|
||||||
employment_insurance: Decimal
|
employment_insurance: Decimal
|
||||||
|
child_support: Decimal = Decimal("0")
|
||||||
income_tax: Decimal
|
income_tax: Decimal
|
||||||
other_deduction: Decimal
|
other_deduction: Decimal
|
||||||
total_deduction: Decimal
|
total_deduction: Decimal
|
||||||
|
|||||||
@@ -89,27 +89,27 @@ class BonusCalculationService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_previous_salary_avg(employee_id: int, payment_date: date) -> Decimal:
|
def get_previous_salary_avg(employee_id: int, payment_date: date) -> Decimal:
|
||||||
"""前月までの3ヶ月平均給与を取得(賞与所得税計算用)"""
|
"""前月の社会保険料等控除後の給与を取得(賞与所得税計算用)
|
||||||
|
|
||||||
|
税法上は「前月分の社会保険料等控除後の給与」= 直前1ヶ月分のみを使用する。
|
||||||
|
ステータス問わず最新の月次給与レコードを参照する。
|
||||||
|
"""
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
# 前月までの3ヶ月の社会保険料等控除後の給与を取得
|
# 賞与支払日より前の直近1ヶ月分の給与を取得(ステータス問わず)
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""
|
"""
|
||||||
SELECT AVG(total_payment - commute_allowance - health_insurance - care_insurance - pension_insurance - employment_insurance - COALESCE(child_support, 0)) as avg_salary
|
SELECT total_payment - commute_allowance - health_insurance - care_insurance - pension_insurance - employment_insurance - COALESCE(child_support, 0) as net_salary
|
||||||
FROM (
|
|
||||||
SELECT total_payment, commute_allowance, health_insurance, care_insurance, pension_insurance, employment_insurance, child_support
|
|
||||||
FROM monthly_payroll
|
FROM monthly_payroll
|
||||||
WHERE employee_id = %s
|
WHERE employee_id = %s
|
||||||
AND payment_date < %s
|
AND payment_date < %s
|
||||||
AND status IN ('approved', 'paid')
|
|
||||||
ORDER BY payment_date DESC
|
ORDER BY payment_date DESC
|
||||||
LIMIT 3
|
LIMIT 1
|
||||||
) as recent_payroll
|
|
||||||
""",
|
""",
|
||||||
(employee_id, payment_date)
|
(employee_id, payment_date)
|
||||||
)
|
)
|
||||||
result = cur.fetchone()
|
result = cur.fetchone()
|
||||||
return Decimal(str(result["avg_salary"])) if result and result["avg_salary"] else Decimal("0")
|
return Decimal(str(result["net_salary"])) if result and result["net_salary"] else Decimal("0")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calculate_bonus_income_tax(
|
def calculate_bonus_income_tax(
|
||||||
@@ -230,11 +230,11 @@ class BonusCalculationService:
|
|||||||
total_bonus * Decimal(str(employment_insurance_rate["employee_rate"]))
|
total_bonus * Decimal(str(employment_insurance_rate["employee_rate"]))
|
||||||
).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
|
||||||
|
|
||||||
# 子ども・子育て支援金(令和8年4月分以降のみ適用)
|
# 子ども・子育て支援金(令和8年5月支給分以降のみ適用)
|
||||||
# 賞与の場合: 社会保険料率設定(insurance_rates)の料率 × 賞与総額で計算
|
# 賞与は bonus_month でなく実際の支給日(payment_date)で判定する
|
||||||
child_support = Decimal("0")
|
child_support = Decimal("0")
|
||||||
is_child_support_applicable = (
|
is_child_support_applicable = (
|
||||||
(bonus_year == 2026 and bonus_month >= 5) or bonus_year >= 2027
|
(payment_date.year == 2026 and payment_date.month >= 5) or payment_date.year >= 2027
|
||||||
)
|
)
|
||||||
if is_child_support_applicable:
|
if is_child_support_applicable:
|
||||||
child_support_rate = BonusCalculationService.get_insurance_rate("子ども・子育て支援金", payment_date)
|
child_support_rate = BonusCalculationService.get_insurance_rate("子ども・子育て支援金", payment_date)
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class BonusVoucherData(BaseModel):
|
|||||||
bonus_year: int
|
bonus_year: int
|
||||||
bonus_month: int
|
bonus_month: int
|
||||||
bonus_type: str = "" # 夏季賞与/冬季賞与/決算賞与/その他
|
bonus_type: str = "" # 夏季賞与/冬季賞与/決算賞与/その他
|
||||||
|
payment_date: Optional[str] = None # 実際の支給日
|
||||||
# 支給項目
|
# 支給項目
|
||||||
base_bonus: Decimal = Decimal(0) # 基本賞与額
|
base_bonus: Decimal = Decimal(0) # 基本賞与額
|
||||||
performance_bonus: Decimal = Decimal(0) # 業績賞与
|
performance_bonus: Decimal = Decimal(0) # 業績賞与
|
||||||
@@ -103,6 +104,7 @@ class BonusVoucherData(BaseModel):
|
|||||||
care_insurance: Decimal = Decimal(0)
|
care_insurance: Decimal = Decimal(0)
|
||||||
pension_insurance: Decimal = Decimal(0)
|
pension_insurance: Decimal = Decimal(0)
|
||||||
employment_insurance: Decimal = Decimal(0)
|
employment_insurance: Decimal = Decimal(0)
|
||||||
|
child_support: Decimal = Decimal(0) # 子ども・子育て支援金
|
||||||
income_tax: Decimal = Decimal(0) # 賞与所得税
|
income_tax: Decimal = Decimal(0) # 賞与所得税
|
||||||
other_deduction: Decimal = Decimal(0)
|
other_deduction: Decimal = Decimal(0)
|
||||||
total_deduction: Decimal = Decimal(0)
|
total_deduction: Decimal = Decimal(0)
|
||||||
|
|||||||
@@ -200,9 +200,10 @@ def get_bonus_data_for_period(
|
|||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
e.employee_id, e.employee_code, e.name,
|
e.employee_id, e.employee_code, e.name,
|
||||||
bp.bonus_year, bp.bonus_month, bp.bonus_type,
|
bp.bonus_year, bp.bonus_month, bp.bonus_type, bp.payment_date,
|
||||||
bp.base_bonus, bp.performance_bonus, bp.other_bonus, bp.total_bonus,
|
bp.base_bonus, bp.performance_bonus, bp.other_bonus, bp.total_bonus,
|
||||||
bp.health_insurance, bp.care_insurance, bp.pension_insurance, bp.employment_insurance,
|
bp.health_insurance, bp.care_insurance, bp.pension_insurance, bp.employment_insurance,
|
||||||
|
COALESCE(bp.child_support, 0) as child_support,
|
||||||
bp.income_tax, bp.other_deduction, bp.total_deduction,
|
bp.income_tax, bp.other_deduction, bp.total_deduction,
|
||||||
bp.net_bonus
|
bp.net_bonus
|
||||||
FROM employees e
|
FROM employees e
|
||||||
@@ -226,6 +227,7 @@ def get_bonus_data_for_period(
|
|||||||
bonus_year=row.get("bonus_year"),
|
bonus_year=row.get("bonus_year"),
|
||||||
bonus_month=row.get("bonus_month"),
|
bonus_month=row.get("bonus_month"),
|
||||||
bonus_type=row.get("bonus_type", ""),
|
bonus_type=row.get("bonus_type", ""),
|
||||||
|
payment_date=str(row.get("payment_date")) if row.get("payment_date") else None,
|
||||||
# 支給項目
|
# 支給項目
|
||||||
base_bonus=Decimal(str(row.get("base_bonus", 0))) if row.get("base_bonus") else Decimal(0),
|
base_bonus=Decimal(str(row.get("base_bonus", 0))) if row.get("base_bonus") else Decimal(0),
|
||||||
performance_bonus=Decimal(str(row.get("performance_bonus", 0))) if row.get("performance_bonus") else Decimal(0),
|
performance_bonus=Decimal(str(row.get("performance_bonus", 0))) if row.get("performance_bonus") else Decimal(0),
|
||||||
@@ -236,6 +238,7 @@ def get_bonus_data_for_period(
|
|||||||
care_insurance=Decimal(str(row.get("care_insurance", 0))) if row.get("care_insurance") else Decimal(0),
|
care_insurance=Decimal(str(row.get("care_insurance", 0))) if row.get("care_insurance") else Decimal(0),
|
||||||
pension_insurance=Decimal(str(row.get("pension_insurance", 0))) if row.get("pension_insurance") else Decimal(0),
|
pension_insurance=Decimal(str(row.get("pension_insurance", 0))) if row.get("pension_insurance") else Decimal(0),
|
||||||
employment_insurance=Decimal(str(row.get("employment_insurance", 0))) if row.get("employment_insurance") else Decimal(0),
|
employment_insurance=Decimal(str(row.get("employment_insurance", 0))) if row.get("employment_insurance") else Decimal(0),
|
||||||
|
child_support=Decimal(str(row.get("child_support", 0))) if row.get("child_support") else Decimal(0),
|
||||||
income_tax=Decimal(str(row.get("income_tax", 0))) if row.get("income_tax") else Decimal(0),
|
income_tax=Decimal(str(row.get("income_tax", 0))) if row.get("income_tax") else Decimal(0),
|
||||||
other_deduction=Decimal(str(row.get("other_deduction", 0))) if row.get("other_deduction") else Decimal(0),
|
other_deduction=Decimal(str(row.get("other_deduction", 0))) if row.get("other_deduction") else Decimal(0),
|
||||||
total_deduction=Decimal(str(row.get("total_deduction", 0))) if row.get("total_deduction") else Decimal(0),
|
total_deduction=Decimal(str(row.get("total_deduction", 0))) if row.get("total_deduction") else Decimal(0),
|
||||||
@@ -254,23 +257,25 @@ def get_bonus_data_for_period(
|
|||||||
bonus_year=row[3],
|
bonus_year=row[3],
|
||||||
bonus_month=row[4],
|
bonus_month=row[4],
|
||||||
bonus_type=row[5] if row[5] else "",
|
bonus_type=row[5] if row[5] else "",
|
||||||
|
payment_date=str(row[6]) if row[6] else None,
|
||||||
# 支給項目
|
# 支給項目
|
||||||
base_bonus=Decimal(str(row[6])) if row[6] else Decimal(0),
|
base_bonus=Decimal(str(row[7])) if row[7] else Decimal(0),
|
||||||
performance_bonus=Decimal(str(row[7])) if row[7] else Decimal(0),
|
performance_bonus=Decimal(str(row[8])) if row[8] else Decimal(0),
|
||||||
other_bonus=Decimal(str(row[8])) if row[8] else Decimal(0),
|
other_bonus=Decimal(str(row[9])) if row[9] else Decimal(0),
|
||||||
total_bonus=Decimal(str(row[9])) if row[9] else Decimal(0),
|
total_bonus=Decimal(str(row[10])) if row[10] else Decimal(0),
|
||||||
# 控除項目
|
# 控除項目
|
||||||
health_insurance=Decimal(str(row[10])) if row[10] else Decimal(0),
|
health_insurance=Decimal(str(row[11])) if row[11] else Decimal(0),
|
||||||
care_insurance=Decimal(str(row[11])) if row[11] else Decimal(0),
|
care_insurance=Decimal(str(row[12])) if row[12] else Decimal(0),
|
||||||
pension_insurance=Decimal(str(row[12])) if row[12] else Decimal(0),
|
pension_insurance=Decimal(str(row[13])) if row[13] else Decimal(0),
|
||||||
employment_insurance=Decimal(str(row[13])) if row[13] else Decimal(0),
|
employment_insurance=Decimal(str(row[14])) if row[14] else Decimal(0),
|
||||||
income_tax=Decimal(str(row[14])) if row[14] else Decimal(0),
|
child_support=Decimal(str(row[15])) if row[15] else Decimal(0),
|
||||||
other_deduction=Decimal(str(row[15])) if row[15] else Decimal(0),
|
income_tax=Decimal(str(row[16])) if row[16] else Decimal(0),
|
||||||
total_deduction=Decimal(str(row[16])) if row[16] else Decimal(0),
|
other_deduction=Decimal(str(row[17])) if row[17] else Decimal(0),
|
||||||
net_bonus=Decimal(str(row[17])) if row[17] else Decimal(0),
|
total_deduction=Decimal(str(row[18])) if row[18] else Decimal(0),
|
||||||
|
net_bonus=Decimal(str(row[19])) if row[19] else Decimal(0),
|
||||||
# レガシー互換性用
|
# レガシー互換性用
|
||||||
bonus_amount=Decimal(str(row[9])) if row[9] else Decimal(0),
|
bonus_amount=Decimal(str(row[10])) if row[10] else Decimal(0),
|
||||||
tax_amount=Decimal(str(row[16])) if row[16] else Decimal(0)
|
tax_amount=Decimal(str(row[18])) if row[18] else Decimal(0)
|
||||||
)
|
)
|
||||||
data.append(bonus_obj)
|
data.append(bonus_obj)
|
||||||
return data
|
return data
|
||||||
|
|||||||
14
check_api_821.py
Normal file
14
check_api_821.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import urllib.request, json
|
||||||
|
url = 'http://192.168.0.61:18000/trial-balance?date_from=2025-06-01&date_to=2025-12-31'
|
||||||
|
with urllib.request.urlopen(url, timeout=5) as r:
|
||||||
|
data = json.loads(r.read())
|
||||||
|
accounts = data['accounts']
|
||||||
|
print('全科目コード:', [a.get('account_code') for a in accounts])
|
||||||
|
print()
|
||||||
|
for a in accounts:
|
||||||
|
code = a.get('account_code', '')
|
||||||
|
if code and code >= '800':
|
||||||
|
name = a.get('account_name', '')
|
||||||
|
debit = a.get('debit', 0) or 0
|
||||||
|
credit = a.get('credit', 0) or 0
|
||||||
|
print(f' {code} {name:<15} debit:{debit:>10,.0f} credit:{credit:>10,.0f}')
|
||||||
20
check_bonus_table.py
Normal file
20
check_bonus_table.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import psycopg2
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# bonusテーブル名確認
|
||||||
|
cur.execute("SELECT table_name FROM information_schema.tables WHERE table_name LIKE '%bonus%' OR table_name LIKE '%employee%'")
|
||||||
|
print("tables:", cur.fetchall())
|
||||||
|
|
||||||
|
# 王珏の賞与データを確認
|
||||||
|
cur.execute("""
|
||||||
|
SELECT b.bonus_id, b.bonus_year, b.bonus_month, b.bonus_type, b.payment_date, b.child_support, b.total_bonus
|
||||||
|
FROM bonus_payments b
|
||||||
|
JOIN employees e ON b.employee_id = e.employee_id
|
||||||
|
WHERE e.employee_code = '20190001'
|
||||||
|
ORDER BY b.bonus_year DESC, b.bonus_month DESC LIMIT 5
|
||||||
|
""")
|
||||||
|
print("bonus data:", cur.fetchall())
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
31
check_child_support_rate.py
Normal file
31
check_child_support_rate.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import psycopg2
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 子ども・子育て支援金の保険料率を確認
|
||||||
|
cur.execute("""
|
||||||
|
SELECT * FROM insurance_rates
|
||||||
|
WHERE rate_type = '子ども・子育て支援金'
|
||||||
|
ORDER BY rate_year, calculation_target
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print("子ども・子育て支援金 rates:")
|
||||||
|
for r in rows:
|
||||||
|
print(r)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# 王珏の賞与再確認(再計算後)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT b.bonus_id, b.bonus_year, b.bonus_month, b.bonus_type, b.payment_date, b.child_support, b.total_bonus
|
||||||
|
FROM bonus_payments b
|
||||||
|
JOIN employees e ON b.employee_id = e.employee_id
|
||||||
|
WHERE e.employee_code = '20190001'
|
||||||
|
ORDER BY b.bonus_year DESC, b.bonus_month DESC LIMIT 5
|
||||||
|
""")
|
||||||
|
print("王珏 bonus:")
|
||||||
|
for r in cur.fetchall():
|
||||||
|
print(r)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
82
check_jan2026_bank.py
Normal file
82
check_jan2026_bank.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import psycopg2
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 普通預金のaccount_idを取得
|
||||||
|
cur.execute("SELECT account_id, account_code, account_name FROM accounts WHERE account_code = '102'")
|
||||||
|
acc = cur.fetchone()
|
||||||
|
print(f"科目: {acc}")
|
||||||
|
account_id = acc[0]
|
||||||
|
|
||||||
|
# 2026年1月の仕訳明細を全件取得
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
jl.line_id,
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit,
|
||||||
|
je.is_latest,
|
||||||
|
je.is_deleted
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = %s
|
||||||
|
AND je.entry_date >= '2026-01-01'
|
||||||
|
AND je.entry_date <= '2026-01-31'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id
|
||||||
|
""", (account_id,))
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print(f"\n全件(is_latest無視): {len(rows)}件")
|
||||||
|
print("-" * 90)
|
||||||
|
debit_all = Decimal(0)
|
||||||
|
credit_all = Decimal(0)
|
||||||
|
for r in rows:
|
||||||
|
jline_id, entry_id, date, desc, debit, credit, is_latest, is_deleted = r
|
||||||
|
d = Decimal(str(debit or 0))
|
||||||
|
c = Decimal(str(credit or 0))
|
||||||
|
debit_all += d
|
||||||
|
credit_all += c
|
||||||
|
print(f"entry_id={entry_id} date={date} is_latest={is_latest} debit={d:>12} credit={c:>12} | {desc[:30]}")
|
||||||
|
|
||||||
|
print(f"\n合計 (全件): debit={debit_all:>12} credit={credit_all:>12}")
|
||||||
|
|
||||||
|
# is_latest=trueのみ
|
||||||
|
cur.execute("""
|
||||||
|
SELECT
|
||||||
|
jl.journal_line_id,
|
||||||
|
je.journal_entry_id,
|
||||||
|
je.entry_date,
|
||||||
|
je.description,
|
||||||
|
jl.debit,
|
||||||
|
jl.credit
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = %s
|
||||||
|
AND je.entry_date >= '2026-01-01'
|
||||||
|
AND je.entry_date <= '2026-01-31'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.is_latest = true
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id
|
||||||
|
""", (account_id,))
|
||||||
|
|
||||||
|
rows2 = cur.fetchall()
|
||||||
|
print(f"\nis_latest=true のみ: {len(rows2)}件")
|
||||||
|
print("-" * 90)
|
||||||
|
debit_latest = Decimal(0)
|
||||||
|
credit_latest = Decimal(0)
|
||||||
|
for r in rows2:
|
||||||
|
line_id, entry_id, date, desc, debit, credit = r
|
||||||
|
d = Decimal(str(debit or 0))
|
||||||
|
c = Decimal(str(credit or 0))
|
||||||
|
debit_latest += d
|
||||||
|
credit_latest += c
|
||||||
|
print(f"entry_id={entry_id} date={date} debit={d:>12} credit={c:>12} | {desc[:40]}")
|
||||||
|
|
||||||
|
print(f"\n合計 (is_latest=true): debit={debit_latest:>12} credit={credit_latest:>12}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
70
check_jan2026_bank2.py
Normal file
70
check_jan2026_bank2.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import psycopg2
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 普通預金のaccount_id
|
||||||
|
cur.execute("SELECT account_id, account_code, account_name FROM accounts WHERE account_code = '102'")
|
||||||
|
acc = cur.fetchone()
|
||||||
|
print(f"科目: {acc}")
|
||||||
|
account_id = acc[0]
|
||||||
|
|
||||||
|
# ===== 全件(is_latest無視)=====
|
||||||
|
cur.execute("""
|
||||||
|
SELECT je.journal_entry_id, je.entry_date, je.description,
|
||||||
|
jl.debit, jl.credit, je.is_latest, je.is_deleted
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = %s
|
||||||
|
AND je.entry_date >= '2026-01-01'
|
||||||
|
AND je.entry_date <= '2026-01-31'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id
|
||||||
|
""", (account_id,))
|
||||||
|
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print(f"\n全件(is_deleted=false): {len(rows)}件")
|
||||||
|
print("-" * 100)
|
||||||
|
debit_all = Decimal(0)
|
||||||
|
credit_all = Decimal(0)
|
||||||
|
for r in rows:
|
||||||
|
entry_id, date, desc, debit, credit, is_latest, is_deleted = r
|
||||||
|
d = Decimal(str(debit or 0))
|
||||||
|
c = Decimal(str(credit or 0))
|
||||||
|
debit_all += d
|
||||||
|
credit_all += c
|
||||||
|
print(f"entry_id={entry_id:5} date={date} is_latest={str(is_latest):5} debit={d:>12} credit={c:>12} | {(desc or '')[:40]}")
|
||||||
|
|
||||||
|
print(f"\n合計 (全件): debit={debit_all:>12} credit={credit_all:>12}")
|
||||||
|
|
||||||
|
# ===== is_latest=true のみ =====
|
||||||
|
cur.execute("""
|
||||||
|
SELECT je.journal_entry_id, je.entry_date, je.description,
|
||||||
|
jl.debit, jl.credit
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE jl.account_id = %s
|
||||||
|
AND je.entry_date >= '2026-01-01'
|
||||||
|
AND je.entry_date <= '2026-01-31'
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.is_latest = true
|
||||||
|
ORDER BY je.entry_date, je.journal_entry_id
|
||||||
|
""", (account_id,))
|
||||||
|
|
||||||
|
rows2 = cur.fetchall()
|
||||||
|
print(f"\nis_latest=true のみ: {len(rows2)}件")
|
||||||
|
print("-" * 100)
|
||||||
|
debit_latest = Decimal(0)
|
||||||
|
credit_latest = Decimal(0)
|
||||||
|
for r in rows2:
|
||||||
|
entry_id, date, desc, debit, credit = r
|
||||||
|
d = Decimal(str(debit or 0))
|
||||||
|
c = Decimal(str(credit or 0))
|
||||||
|
debit_latest += d
|
||||||
|
credit_latest += c
|
||||||
|
print(f"entry_id={entry_id:5} date={date} debit={d:>12} credit={c:>12} | {(desc or '')[:40]}")
|
||||||
|
|
||||||
|
print(f"\n合計 (is_latest=true): debit={debit_latest:>12} credit={credit_latest:>12}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
6
check_jl_cols.py
Normal file
6
check_jl_cols.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import psycopg2
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT column_name FROM information_schema.columns WHERE table_name='journal_lines' ORDER BY ordinal_position")
|
||||||
|
for r in cur.fetchall(): print(r[0])
|
||||||
|
conn.close()
|
||||||
65
check_salary_bonus.py
Normal file
65
check_salary_bonus.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import psycopg2
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT a.account_code, a.account_name,
|
||||||
|
COALESCE(SUM(jl.debit),0) AS debit,
|
||||||
|
COALESCE(SUM(jl.credit),0) AS credit,
|
||||||
|
COALESCE(SUM(jl.debit),0) - COALESCE(SUM(jl.credit),0) AS balance
|
||||||
|
FROM accounts a
|
||||||
|
JOIN journal_lines jl ON a.account_id = jl.account_id
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.is_latest = true AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01' AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_type = 'expense'
|
||||||
|
GROUP BY a.account_id, a.account_code, a.account_name
|
||||||
|
HAVING COALESCE(SUM(jl.debit),0) + COALESCE(SUM(jl.credit),0) > 0
|
||||||
|
ORDER BY a.account_code
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print("=== 費用科目 残高試算表(2025年6月〜12月)===")
|
||||||
|
print(f"{'科目CD':<6} {'科目名':<15} {'期間借方':>12} {'期間貸方':>12} {'残高':>12}")
|
||||||
|
print("-" * 62)
|
||||||
|
total_bal = 0
|
||||||
|
for r in rows:
|
||||||
|
print(f"{r[0]:<6} {r[1]:<15} {r[2]:>12,.0f} {r[3]:>12,.0f} {r[4]:>12,.0f}")
|
||||||
|
total_bal += r[4]
|
||||||
|
print("-" * 62)
|
||||||
|
print(f"{'費用合計':<22} {total_bal:>12,.0f}")
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT COALESCE(SUM(jl.credit),0) - COALESCE(SUM(jl.debit),0)
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.is_latest = true AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01' AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_type = 'revenue'
|
||||||
|
""")
|
||||||
|
total_rev = cur.fetchone()[0]
|
||||||
|
print()
|
||||||
|
print(f"収益合計 : {total_rev:>12,.0f}")
|
||||||
|
print(f"当期純損益 : {total_rev - total_bal:>12,.0f}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT je.entry_date, je.description, jl.debit, jl.credit, jl.line_description
|
||||||
|
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 je.is_latest = true AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01' AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_code = '813'
|
||||||
|
ORDER BY je.entry_date
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print()
|
||||||
|
print('=== 813給料手当 の仕訳明細 ===')
|
||||||
|
for r in rows:
|
||||||
|
desc = str(r[4]) if r[4] else ''
|
||||||
|
print(f' {r[0]} {str(r[1])[:45]:<45} 借:{r[2] or 0:>10,.0f} 貸:{r[3] or 0:>10,.0f} {desc}')
|
||||||
|
|
||||||
|
conn.close()
|
||||||
140
compare_with_accountant.py
Normal file
140
compare_with_accountant.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
"""
|
||||||
|
税理士の残高試算表とシステムのデータを比較するスクリプト
|
||||||
|
期間: 2025年6月1日〜2025年12月31日
|
||||||
|
"""
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
|
conn = psycopg2.connect(
|
||||||
|
host='192.168.0.61',
|
||||||
|
port=55432,
|
||||||
|
dbname='njts_acct',
|
||||||
|
user='njts_app',
|
||||||
|
password='njts_app2025'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print("【システム】損益計算書 (2025年6月1日〜2025年12月31日)")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
# Get P&L accounts breakdown (account_type: 収益・費用 etc.)
|
||||||
|
query = """
|
||||||
|
SELECT
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
a.account_type,
|
||||||
|
COALESCE(SUM(jl.debit), 0) AS total_debit,
|
||||||
|
COALESCE(SUM(jl.credit), 0) AS total_credit,
|
||||||
|
CASE
|
||||||
|
WHEN a.account_type IN ('revenue', '収益', 'income')
|
||||||
|
THEN COALESCE(SUM(jl.credit), 0) - COALESCE(SUM(jl.debit), 0)
|
||||||
|
ELSE
|
||||||
|
COALESCE(SUM(jl.debit), 0) - COALESCE(SUM(jl.credit), 0)
|
||||||
|
END AS balance
|
||||||
|
FROM accounts a
|
||||||
|
LEFT JOIN journal_lines jl ON a.account_id = jl.account_id
|
||||||
|
LEFT JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
AND je.is_latest = true
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01'
|
||||||
|
AND je.entry_date <= '2025-12-31'
|
||||||
|
WHERE a.account_type NOT IN ('asset', 'liability', 'equity', '資産', '負債', '純資産', '資本')
|
||||||
|
GROUP BY a.account_id, a.account_code, a.account_name, a.account_type
|
||||||
|
HAVING COALESCE(SUM(jl.debit), 0) + COALESCE(SUM(jl.credit), 0) != 0
|
||||||
|
ORDER BY a.account_code
|
||||||
|
"""
|
||||||
|
|
||||||
|
cur.execute(query)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
print(f"{'科目コード':<10} {'科目名':<20} {'種別':<15} {'借方合計':>12} {'貸方合計':>12} {'残高':>12}")
|
||||||
|
print("-" * 85)
|
||||||
|
for row in rows:
|
||||||
|
print(f"{str(row[0]):<10} {str(row[1]):<20} {str(row[2]):<15} {row[3]:>12,.0f} {row[4]:>12,.0f} {row[5]:>12,.0f}")
|
||||||
|
else:
|
||||||
|
print("データが見つかりません。account_typeを確認します...")
|
||||||
|
cur.execute("SELECT DISTINCT account_type FROM accounts ORDER BY account_type")
|
||||||
|
types = cur.fetchall()
|
||||||
|
print("account_typeの一覧:", [t[0] for t in types])
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【システム】勘定科目別集計(全科目)")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
query2 = """
|
||||||
|
SELECT
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
a.account_type,
|
||||||
|
COALESCE(SUM(jl.debit), 0) AS total_debit,
|
||||||
|
COALESCE(SUM(jl.credit), 0) AS total_credit
|
||||||
|
FROM accounts a
|
||||||
|
JOIN journal_lines jl ON a.account_id = jl.account_id
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.is_latest = true
|
||||||
|
AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01'
|
||||||
|
AND je.entry_date <= '2025-12-31'
|
||||||
|
GROUP BY a.account_id, a.account_code, a.account_name, a.account_type
|
||||||
|
ORDER BY a.account_code
|
||||||
|
"""
|
||||||
|
|
||||||
|
cur.execute(query2)
|
||||||
|
rows2 = cur.fetchall()
|
||||||
|
|
||||||
|
if rows2:
|
||||||
|
print(f"{'科目コード':<10} {'科目名':<25} {'種別':<15} {'期間借方':>12} {'期間貸方':>12}")
|
||||||
|
print("-" * 80)
|
||||||
|
for row in rows2:
|
||||||
|
print(f"{str(row[0]):<10} {str(row[1]):<25} {str(row[2]):<15} {row[3]:>12,.0f} {row[4]:>12,.0f}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 80)
|
||||||
|
print("【比較】税理士試算表との差異分析")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
# Tax accountant figures from PDF
|
||||||
|
accountant_figures = {
|
||||||
|
"売上高": 28896368,
|
||||||
|
"役員報酬": 2520000,
|
||||||
|
"給料手当": 2700000,
|
||||||
|
"賞与": 4300000,
|
||||||
|
"法定福利費": 1588604,
|
||||||
|
"福利厚生費": 242978,
|
||||||
|
"外注費": 7490000,
|
||||||
|
"交際費": 915007,
|
||||||
|
"会議費": 219070,
|
||||||
|
"旅費交通費": 522149,
|
||||||
|
"通信費": 31459,
|
||||||
|
"消耗品費": 1027007,
|
||||||
|
"修繕費": 986,
|
||||||
|
"新聞図書費": 9900,
|
||||||
|
"支払手数料": 168800,
|
||||||
|
"地代家賃": 1081822,
|
||||||
|
"保険料": 33950,
|
||||||
|
"租税公課": 33100,
|
||||||
|
"受取利息": 4878,
|
||||||
|
"法人税住民税事業税": 747,
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n税理士試算表の数値:")
|
||||||
|
total_revenue = 0
|
||||||
|
total_expense = 0
|
||||||
|
for k, v in accountant_figures.items():
|
||||||
|
if k in ["売上高", "受取利息"]:
|
||||||
|
total_revenue += v
|
||||||
|
print(f" {k:<20}: {v:>12,.0f} (収益)")
|
||||||
|
else:
|
||||||
|
total_expense += v
|
||||||
|
print(f" {k:<20}: {v:>12,.0f} (費用)")
|
||||||
|
|
||||||
|
print(f"\n 収益合計 : {total_revenue:>12,.0f}")
|
||||||
|
print(f" 費用合計 : {total_expense:>12,.0f}")
|
||||||
|
print(f" 当期純損益 : {total_revenue - total_expense:>12,.0f}")
|
||||||
|
print(f"\n 税理士の当期純損益: 6,015,667")
|
||||||
|
print(f" 自社の当期純損益 : 9,461,950")
|
||||||
|
print(f" 差 額 : {9461950 - 6015667:>12,.0f}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
Binary file not shown.
BIN
docs/残高試算表20250601-20251231.PDF
Normal file
BIN
docs/残高試算表20250601-20251231.PDF
Normal file
Binary file not shown.
33
fix_jan2026.py
Normal file
33
fix_jan2026.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import psycopg2
|
||||||
|
import requests
|
||||||
|
|
||||||
|
conn = psycopg2.connect(host='192.168.0.61', port=55432, dbname='njts_acct', user='njts_app', password='njts_app2025')
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# 一時的に承認解除
|
||||||
|
cur.execute("UPDATE monthly_payroll SET status='calculated' WHERE payroll_id IN (10,11)")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("承認解除完了")
|
||||||
|
|
||||||
|
# 再計算
|
||||||
|
for pid in [10, 11]:
|
||||||
|
r = requests.post('http://192.168.0.61:18000/payroll/calculation/' + str(pid) + '/recalculate', json={})
|
||||||
|
d = r.json()
|
||||||
|
if 'detail' in d:
|
||||||
|
print('id=' + str(pid) + ' ERROR: ' + str(d['detail']))
|
||||||
|
else:
|
||||||
|
keys = ['health_insurance', 'care_insurance', 'pension_insurance',
|
||||||
|
'employment_insurance', 'child_support', 'income_tax', 'resident_tax', 'other_deduction']
|
||||||
|
comp = sum(float(d.get(k) or 0) for k in keys)
|
||||||
|
td = float(d.get('total_deduction') or 0)
|
||||||
|
print('id=' + str(pid) +
|
||||||
|
' health=' + str(d['health_insurance']) +
|
||||||
|
' care=' + str(d['care_insurance']) +
|
||||||
|
' income_tax=' + str(d['income_tax']) +
|
||||||
|
' total_ded=' + str(td) +
|
||||||
|
' SUM=' + str(comp) +
|
||||||
|
' DIFF=' + str(td - comp) +
|
||||||
|
' net=' + str(d['net_payment']))
|
||||||
|
|
||||||
|
print("完了")
|
||||||
@@ -29,10 +29,17 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.card h2 {
|
.card h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
.card p:last-child {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
.btn {
|
.btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
@@ -40,6 +47,7 @@
|
|||||||
background: #007bff;
|
background: #007bff;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
max-width: 980px;
|
max-width: 980px;
|
||||||
|
|||||||
@@ -861,6 +861,7 @@
|
|||||||
<div class="search-buttons">
|
<div class="search-buttons">
|
||||||
<button onclick="searchJournals()">🔍 検索</button>
|
<button onclick="searchJournals()">🔍 検索</button>
|
||||||
<button onclick="printSearchResults()">🖨️ 印刷</button>
|
<button onclick="printSearchResults()">🖨️ 印刷</button>
|
||||||
|
<button onclick="clearSearchConditions()" style="background: #6c757d; color: white; border: none; padding: 6px 14px; border-radius: 4px; cursor: pointer;">🔄 条件クリア</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2394,6 +2395,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const printArea = document.getElementById("printArea");
|
const printArea = document.getElementById("printArea");
|
||||||
|
// 印刷後に元の検索結果を復元できるよう保存
|
||||||
|
const savedSearchHtml = printArea.innerHTML;
|
||||||
printArea.innerHTML = "";
|
printArea.innerHTML = "";
|
||||||
|
|
||||||
// 月ごとにデータをグループ化
|
// 月ごとにデータをグループ化
|
||||||
@@ -2525,6 +2528,13 @@
|
|||||||
|
|
||||||
printArea.appendChild(section);
|
printArea.appendChild(section);
|
||||||
});
|
});
|
||||||
|
// 印刷後に元の検索結果を復元する
|
||||||
|
const restoreAfterPrint = () => {
|
||||||
|
printArea.innerHTML = savedSearchHtml;
|
||||||
|
window.removeEventListener("afterprint", restoreAfterPrint);
|
||||||
|
};
|
||||||
|
window.addEventListener("afterprint", restoreAfterPrint);
|
||||||
|
|
||||||
// 少し待ってから印刷(データの表示を待つ)
|
// 少し待ってから印刷(データの表示を待つ)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.print();
|
window.print();
|
||||||
@@ -2552,6 +2562,25 @@
|
|||||||
searchJournals();
|
searchJournals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 検索条件クリア(期間以外)
|
||||||
|
function clearSearchConditions() {
|
||||||
|
// 科目
|
||||||
|
const accountInput = document.getElementById("searchAccountInput");
|
||||||
|
if (accountInput) {
|
||||||
|
accountInput.value = "";
|
||||||
|
accountInput.dataset.accountId = "";
|
||||||
|
}
|
||||||
|
// 摘要
|
||||||
|
const keyword = document.getElementById("searchKeyword");
|
||||||
|
if (keyword) keyword.value = "";
|
||||||
|
// 方向
|
||||||
|
const side = document.getElementById("searchAccountSide");
|
||||||
|
if (side) side.value = "";
|
||||||
|
// 修正履歴を含む
|
||||||
|
const includeHistory = document.getElementById("searchIncludeHistory");
|
||||||
|
if (includeHistory) includeHistory.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 检索结果を表に表示
|
// 检索结果を表に表示
|
||||||
async function searchJournals() {
|
async function searchJournals() {
|
||||||
try {
|
try {
|
||||||
@@ -2784,6 +2813,62 @@
|
|||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
// 行数で振り分け: 3行以上 → 複数行詳細フォーム、2行以下 → 簡易フォーム
|
||||||
|
if (data.lines && data.lines.length > 2) {
|
||||||
|
copyJournalToDetailForm(data);
|
||||||
|
} else {
|
||||||
|
copyJournalToSimpleForm(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
showAlert(`エラーが発生しました: ${error.message}`, "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 複数行フォームへのコピー
|
||||||
|
function copyJournalToDetailForm(data) {
|
||||||
|
document.getElementById("detailEntryDate").value = data.entry_date;
|
||||||
|
document.getElementById("detailDescription").value = data.description || "";
|
||||||
|
|
||||||
|
// 既存行をクリア
|
||||||
|
document.getElementById("detailLinesTbody").innerHTML = "";
|
||||||
|
detailRowSeq = 0;
|
||||||
|
|
||||||
|
// 各明細行をコピー
|
||||||
|
data.lines.forEach((line) => {
|
||||||
|
const tr = detailAddLine();
|
||||||
|
const accountInput = tr.querySelector(".detail-account-input");
|
||||||
|
const acc = accounts.find((a) => a.account_id === line.account_id);
|
||||||
|
if (acc) {
|
||||||
|
accountInput.value = `${acc.account_code} ${acc.account_name}`;
|
||||||
|
accountInput.dataset.accountId = acc.account_id;
|
||||||
|
}
|
||||||
|
if (Number(line.debit) > 0) {
|
||||||
|
tr.querySelector(".detail-debit").value = Number(line.debit).toLocaleString();
|
||||||
|
}
|
||||||
|
if (Number(line.credit) > 0) {
|
||||||
|
tr.querySelector(".detail-credit").value = Number(line.credit).toLocaleString();
|
||||||
|
}
|
||||||
|
if (line.line_description) {
|
||||||
|
tr.querySelector(".detail-line-memo").value = line.line_description;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
detailUpdateTotals();
|
||||||
|
|
||||||
|
// 詳細セクションを開いてスクロール
|
||||||
|
const detailSection = document.getElementById("detailSection");
|
||||||
|
detailSection.open = true;
|
||||||
|
detailSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
|
|
||||||
|
showAlert(
|
||||||
|
`仕訳を複製しました(複数行モード)。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||||
|
"success",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 簡易フォームへのコピー(従来ロジック)
|
||||||
|
function copyJournalToSimpleForm(data) {
|
||||||
// 日付をコピー
|
// 日付をコピー
|
||||||
document.getElementById("entryDate").value = data.entry_date;
|
document.getElementById("entryDate").value = data.entry_date;
|
||||||
|
|
||||||
@@ -2961,10 +3046,6 @@
|
|||||||
`仕訳を複製しました。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
`仕訳を複製しました。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||||
"success",
|
"success",
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
console.error("Error:", error);
|
|
||||||
showAlert(`エラーが発生しました: ${error.message}`, "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
<meta
|
||||||
|
http-equiv="Cache-Control"
|
||||||
|
content="no-cache, no-store, must-revalidate"
|
||||||
|
/>
|
||||||
<meta http-equiv="Pragma" content="no-cache" />
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
<meta http-equiv="Expires" content="0" />
|
<meta http-equiv="Expires" content="0" />
|
||||||
<script src="js/auth.js"></script>
|
<script src="js/auth.js"></script>
|
||||||
@@ -241,9 +244,14 @@
|
|||||||
<div id="tab-salary" class="tab-content-calc active">
|
<div id="tab-salary" class="tab-content-calc active">
|
||||||
<div class="split-layout">
|
<div class="split-layout">
|
||||||
<div class="split-left">
|
<div class="split-left">
|
||||||
<div class="filters" style="display:flex; flex-direction:column; gap:8px;">
|
<div
|
||||||
<label style="display:flex; align-items:center; gap:8px;">
|
class="filters"
|
||||||
<span style="white-space:nowrap; min-width:60px;">対象年:</span>
|
style="display: flex; flex-direction: column; gap: 8px"
|
||||||
|
>
|
||||||
|
<label style="display: flex; align-items: center; gap: 8px">
|
||||||
|
<span style="white-space: nowrap; min-width: 60px"
|
||||||
|
>対象年:</span
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
id="filterYear"
|
id="filterYear"
|
||||||
@@ -253,24 +261,37 @@
|
|||||||
max="2099"
|
max="2099"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label style="display:flex; align-items:center; gap:8px;">
|
<label style="display: flex; align-items: center; gap: 8px">
|
||||||
<span style="white-space:nowrap; min-width:60px;">従業員:</span>
|
<span style="white-space: nowrap; min-width: 60px"
|
||||||
<select id="filterEmployee" style="flex:1;">
|
>従業員:</span
|
||||||
|
>
|
||||||
|
<select id="filterEmployee" style="flex: 1">
|
||||||
<option value="">全員</option>
|
<option value="">全員</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div style="display:flex; gap:8px;">
|
<div style="display: flex; gap: 8px">
|
||||||
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button>
|
<button class="btn btn-primary" onclick="loadPayrolls()">
|
||||||
<button class="btn btn-success" onclick="showCalculateForm()">新規計算</button>
|
検索
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success" onclick="showCalculateForm()">
|
||||||
|
新規計算
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="payrollList" class="payroll-list"></div>
|
<div id="payrollList" class="payroll-list"></div>
|
||||||
</div><!-- /split-left -->
|
</div>
|
||||||
|
<!-- /split-left -->
|
||||||
<div class="split-right" id="splitRightSalary">
|
<div class="split-right" id="splitRightSalary">
|
||||||
<div id="payrollDetail" class="payroll-detail" style="display:none"></div>
|
<div
|
||||||
</div><!-- /split-right -->
|
id="payrollDetail"
|
||||||
</div><!-- /split-layout -->
|
class="payroll-detail"
|
||||||
|
style="display: none"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<!-- /split-right -->
|
||||||
|
</div>
|
||||||
|
<!-- /split-layout -->
|
||||||
|
|
||||||
<!-- 給与計算フォーム(モーダル風) -->
|
<!-- 給与計算フォーム(モーダル風) -->
|
||||||
<div
|
<div
|
||||||
@@ -472,7 +493,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 賞与計算タブ -->
|
<!-- 賞与計算タブ -->
|
||||||
@@ -491,18 +511,27 @@
|
|||||||
max="2099"
|
max="2099"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button class="btn btn-primary" onclick="loadBonus()">検索</button>
|
<button class="btn btn-primary" onclick="loadBonus()">
|
||||||
|
検索
|
||||||
|
</button>
|
||||||
<button class="btn btn-success" onclick="showBonusForm()">
|
<button class="btn btn-success" onclick="showBonusForm()">
|
||||||
新規計算
|
新規計算
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="bonusList" class="payroll-list"></div>
|
<div id="bonusList" class="payroll-list"></div>
|
||||||
</div><!-- /split-left -->
|
</div>
|
||||||
|
<!-- /split-left -->
|
||||||
<div class="split-right" id="splitRightBonus">
|
<div class="split-right" id="splitRightBonus">
|
||||||
<div id="bonusDetail" class="payroll-detail" style="display:none"></div>
|
<div
|
||||||
</div><!-- /split-right -->
|
id="bonusDetail"
|
||||||
</div><!-- /split-layout -->
|
class="payroll-detail"
|
||||||
|
style="display: none"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<!-- /split-right -->
|
||||||
|
</div>
|
||||||
|
<!-- /split-layout -->
|
||||||
|
|
||||||
<!-- 賞与計算フォーム -->
|
<!-- 賞与計算フォーム -->
|
||||||
<div
|
<div
|
||||||
@@ -637,7 +666,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 账票出力タブ -->
|
<!-- 账票出力タブ -->
|
||||||
@@ -1750,20 +1778,64 @@
|
|||||||
|
|
||||||
// 計算明細表示用の変数(賞与は総支給額に料率を乗じる)
|
// 計算明細表示用の変数(賞与は総支給額に料率を乗じる)
|
||||||
const _bHealth = Number(bonus.health_insurance || 0);
|
const _bHealth = Number(bonus.health_insurance || 0);
|
||||||
|
|
||||||
|
// 源泉税控除対象人数を計算(給与と同じロジック)
|
||||||
|
const bDependentCount = employee.dependents
|
||||||
|
? employee.dependents.filter((d) => {
|
||||||
|
if (d.valid_to && new Date(d.valid_to) < new Date()) return false;
|
||||||
|
if (!d.birth_date) return false;
|
||||||
|
const birthDate = new Date(d.birth_date);
|
||||||
|
const targetYear = bonus.bonus_year;
|
||||||
|
const yearEnd = new Date(targetYear, 11, 31);
|
||||||
|
let age = yearEnd.getFullYear() - birthDate.getFullYear();
|
||||||
|
const monthDiff = yearEnd.getMonth() - birthDate.getMonth();
|
||||||
|
if (monthDiff < 0 || (monthDiff === 0 && yearEnd.getDate() < birthDate.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
return age >= 16;
|
||||||
|
}).length
|
||||||
|
: 0;
|
||||||
const _bCare = Number(bonus.care_insurance || 0);
|
const _bCare = Number(bonus.care_insurance || 0);
|
||||||
const _bPension = Number(bonus.pension_insurance || 0);
|
const _bPension = Number(bonus.pension_insurance || 0);
|
||||||
const _bEmp = Number(bonus.employment_insurance || 0);
|
const _bEmp = Number(bonus.employment_insurance || 0);
|
||||||
const _bChild = Number(bonus.child_support || 0);
|
const _bChild = Number(bonus.child_support || 0);
|
||||||
const _bTotal = Number(bonus.total_bonus || 0);
|
const _bTotal = Number(bonus.total_bonus || 0);
|
||||||
const _bHRateFull = _bTotal > 0 && _bHealth > 0 ? (((_bHealth * 2) / _bTotal) * 100).toFixed(3) : "-";
|
const _bHRateFull =
|
||||||
const _bHRateHalf = _bTotal > 0 && _bHealth > 0 ? ((_bHealth / _bTotal) * 100).toFixed(3) : "-";
|
_bTotal > 0 && _bHealth > 0
|
||||||
const _bCRateFull = _bTotal > 0 && _bCare > 0 ? (((_bCare * 2) / _bTotal) * 100).toFixed(3) : null;
|
? (((_bHealth * 2) / _bTotal) * 100).toFixed(3)
|
||||||
const _bCRateHalf = _bTotal > 0 && _bCare > 0 ? ((_bCare / _bTotal) * 100).toFixed(3) : null;
|
: "-";
|
||||||
const _bPRateFull = _bTotal > 0 && _bPension > 0 ? (((_bPension * 2) / _bTotal) * 100).toFixed(3) : "-";
|
const _bHRateHalf =
|
||||||
const _bPRateHalf = _bTotal > 0 && _bPension > 0 ? ((_bPension / _bTotal) * 100).toFixed(3) : "-";
|
_bTotal > 0 && _bHealth > 0
|
||||||
const _bEmpRate = _bTotal > 0 && _bEmp > 0 ? ((_bEmp / _bTotal) * 100).toFixed(3) : "-";
|
? ((_bHealth / _bTotal) * 100).toFixed(3)
|
||||||
const _bCSRateFull = _bTotal > 0 && _bChild > 0 ? (((_bChild * 2) / _bTotal) * 100).toFixed(4) : null;
|
: "-";
|
||||||
const _bCSRateHalf = _bTotal > 0 && _bChild > 0 ? ((_bChild / _bTotal) * 100).toFixed(4) : null;
|
const _bCRateFull =
|
||||||
|
_bTotal > 0 && _bCare > 0
|
||||||
|
? (((_bCare * 2) / _bTotal) * 100).toFixed(3)
|
||||||
|
: null;
|
||||||
|
const _bCRateHalf =
|
||||||
|
_bTotal > 0 && _bCare > 0
|
||||||
|
? ((_bCare / _bTotal) * 100).toFixed(3)
|
||||||
|
: null;
|
||||||
|
const _bPRateFull =
|
||||||
|
_bTotal > 0 && _bPension > 0
|
||||||
|
? (((_bPension * 2) / _bTotal) * 100).toFixed(3)
|
||||||
|
: "-";
|
||||||
|
const _bPRateHalf =
|
||||||
|
_bTotal > 0 && _bPension > 0
|
||||||
|
? ((_bPension / _bTotal) * 100).toFixed(3)
|
||||||
|
: "-";
|
||||||
|
const _bEmpRate =
|
||||||
|
_bTotal > 0 && _bEmp > 0
|
||||||
|
? ((_bEmp / _bTotal) * 100).toFixed(3)
|
||||||
|
: "-";
|
||||||
|
const _bCSRateFull =
|
||||||
|
_bTotal > 0 && _bChild > 0
|
||||||
|
? (((_bChild * 2) / _bTotal) * 100).toFixed(4)
|
||||||
|
: null;
|
||||||
|
const _bCSRateHalf =
|
||||||
|
_bTotal > 0 && _bChild > 0
|
||||||
|
? ((_bChild / _bTotal) * 100).toFixed(4)
|
||||||
|
: null;
|
||||||
const _bSocialSub = _bHealth + _bCare + _bPension + _bEmp + _bChild;
|
const _bSocialSub = _bHealth + _bCare + _bPension + _bEmp + _bChild;
|
||||||
const _bTaxable = _bTotal - _bSocialSub;
|
const _bTaxable = _bTotal - _bSocialSub;
|
||||||
|
|
||||||
@@ -1815,7 +1887,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail-row"><span>雇用保険:</span><span>¥${_bEmp.toLocaleString()}</span></div>
|
<div class="detail-row"><span>雇用保険:</span><span>¥${_bEmp.toLocaleString()}</span></div>
|
||||||
<div class="calc-note">
|
<div class="calc-note">
|
||||||
${_bEmp > 0
|
${
|
||||||
|
_bEmp > 0
|
||||||
? `¥${_bTotal.toLocaleString()}(賞与総支給額)× ${_bEmpRate}%(雇用保険料率・従業員負担分)`
|
? `¥${_bTotal.toLocaleString()}(賞与総支給額)× ${_bEmpRate}%(雇用保険料率・従業員負担分)`
|
||||||
: "賞与に対する雇用保険は別途計算方式による(または0円)"
|
: "賞与に対する雇用保険は別途計算方式による(または0円)"
|
||||||
}
|
}
|
||||||
@@ -1829,12 +1902,13 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_bSocialSub.toLocaleString()}</span></div>
|
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_bSocialSub.toLocaleString()}</span></div>
|
||||||
<div class="detail-row"><span>所得税:</span><span>¥${Number(
|
<div class="detail-row">
|
||||||
bonus.income_tax || 0,
|
<span>所得税 <small style="color:#666;">(甲欄${bDependentCount}人)</small>:</span>
|
||||||
).toLocaleString()}</span></div>
|
<span>¥${Number(bonus.income_tax || 0).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
<div class="calc-note">
|
<div class="calc-note">
|
||||||
課税対象額 = 賞与総支給額 ¥${_bTotal.toLocaleString()} − 社会保険料計 ¥${_bSocialSub.toLocaleString()} = ¥${_bTaxable.toLocaleString()}<br>
|
課税対象額 = 賞与総支給額 ¥${_bTotal.toLocaleString()} − 社会保険料計 ¥${_bSocialSub.toLocaleString()} = ¥${_bTaxable.toLocaleString()}<br>
|
||||||
→ 賞与に対する源泉徴収税額の算出率の表を参照して計算
|
→ 賞与に対する源泉徴収税額の算出率の表(甲欄・扶養${bDependentCount}人)を参照して計算
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-row"><span>住民税:</span><span>¥${Number(
|
<div class="detail-row"><span>住民税:</span><span>¥${Number(
|
||||||
bonus.resident_tax || 0,
|
bonus.resident_tax || 0,
|
||||||
@@ -1856,6 +1930,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" onclick="editBonus(${
|
||||||
|
bonus.bonus_id
|
||||||
|
})">編集</button>
|
||||||
<button class="btn btn-primary" onclick="recalculateBonus(${
|
<button class="btn btn-primary" onclick="recalculateBonus(${
|
||||||
bonus.bonus_id
|
bonus.bonus_id
|
||||||
})">再計算</button>
|
})">再計算</button>
|
||||||
@@ -1872,6 +1949,105 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function editBonus(bonusId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/payroll/bonus/${bonusId}`);
|
||||||
|
const bonus = await response.json();
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 600px; max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000;">
|
||||||
|
<h2>賞与データ編集</h2>
|
||||||
|
<form id="editBonusForm" onsubmit="saveBonusEdit(event, ${bonusId})">
|
||||||
|
<div class="detail-section">
|
||||||
|
<h3>基本情報</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>支給日</label>
|
||||||
|
<input type="date" name="payment_date" value="${bonus.payment_date}" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>賞与種別</label>
|
||||||
|
<select name="bonus_type">
|
||||||
|
<option value="夏季賞与" ${bonus.bonus_type === '夏季賞与' ? 'selected' : ''}>夏季賞与</option>
|
||||||
|
<option value="冬季賞与" ${bonus.bonus_type === '冬季賞与' ? 'selected' : ''}>冬季賞与</option>
|
||||||
|
<option value="決算賞与" ${bonus.bonus_type === '決算賞与' ? 'selected' : ''}>決算賞与</option>
|
||||||
|
<option value="その他" ${bonus.bonus_type === 'その他' ? 'selected' : ''}>その他</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-section">
|
||||||
|
<h3>支給項目</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>基本賞与額</label>
|
||||||
|
<input type="number" name="base_bonus" step="1" value="${bonus.base_bonus}" required />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>業績賞与</label>
|
||||||
|
<input type="number" name="performance_bonus" step="1" value="${bonus.performance_bonus}" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>その他賞与</label>
|
||||||
|
<input type="number" name="other_bonus" step="1" value="${bonus.other_bonus}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-section">
|
||||||
|
<h3>控除項目(手動入力)</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>その他控除</label>
|
||||||
|
<input type="number" name="other_deduction" step="1" value="${bonus.other_deduction}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:12px; color:#666;">※保存後に再計算を行うと社会保険料・所得税が自動更新されます。</p>
|
||||||
|
<button type="submit" class="btn btn-primary">保存して再計算</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="this.closest('div').parentElement.remove()">キャンセル</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const editDiv = document.createElement("div");
|
||||||
|
editDiv.innerHTML = html;
|
||||||
|
document.body.appendChild(editDiv);
|
||||||
|
} catch (error) {
|
||||||
|
alert("賞与データの取得に失敗しました");
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveBonusEdit(event, bonusId) {
|
||||||
|
event.preventDefault();
|
||||||
|
const form = event.target;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const data = {};
|
||||||
|
formData.forEach((value, key) => {
|
||||||
|
data[key] = isNaN(value) || value === "" ? value : Number(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/payroll/bonus/${bonusId}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert("賞与データを更新しました");
|
||||||
|
form.closest("div").parentElement.remove();
|
||||||
|
await recalculateBonus(bonusId, true);
|
||||||
|
} else {
|
||||||
|
const error = await response.json();
|
||||||
|
alert("更新に失敗しました: " + error.detail);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert("更新に失敗しました");
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteBonus(bonusId) {
|
async function deleteBonus(bonusId) {
|
||||||
if (
|
if (
|
||||||
!confirm("この賞与データを削除しますか?\nこの操作は取り消せません。")
|
!confirm("この賞与データを削除しますか?\nこの操作は取り消せません。")
|
||||||
@@ -1899,8 +2075,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function recalculateBonus(bonusId) {
|
async function recalculateBonus(bonusId, skipConfirm = false) {
|
||||||
if (!confirm("賞与を再計算しますか?")) return;
|
if (!skipConfirm && !confirm("賞与を再計算しますか?")) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -2270,10 +2446,14 @@
|
|||||||
bonuses.forEach((bonus) => {
|
bonuses.forEach((bonus) => {
|
||||||
const bonusYearMonth = `${bonus.bonus_year}-${bonus.bonus_month}`;
|
const bonusYearMonth = `${bonus.bonus_year}-${bonus.bonus_month}`;
|
||||||
const currentKey_new = `${emp.employee_id}-${bonusYearMonth}-bonus`;
|
const currentKey_new = `${emp.employee_id}-${bonusYearMonth}-bonus`;
|
||||||
const bonusPaymentDate = `${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月${getLastDayOfMonth(bonus.bonus_year, bonus.bonus_month)}日`;
|
// DBに保存された実際の支給日を使用。なければ月末日を計算
|
||||||
|
const bonusPaymentDate = bonus.payment_date
|
||||||
// 新しいキーが異なる場合は新しいページを開く
|
? (() => {
|
||||||
if (currentKey !== currentKey_new) {
|
const d = new Date(bonus.payment_date);
|
||||||
|
return `${d.getFullYear()}年${String(d.getMonth()+1).padStart(2,'0')}月${String(d.getDate()).padStart(2,'0')}日`;
|
||||||
|
})()
|
||||||
|
: `${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月${getLastDayOfMonth(bonus.bonus_year, bonus.bonus_month)}日`;
|
||||||
|
if (currentKey && currentKey !== currentKey_new) {
|
||||||
if (pageOpen) {
|
if (pageOpen) {
|
||||||
printHtml += "</div>";
|
printHtml += "</div>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,9 +215,13 @@
|
|||||||
<li>給与計算と賞与計算で異なる料率を設定できます</li>
|
<li>給与計算と賞与計算で異なる料率を設定できます</li>
|
||||||
<li>事業所所在地(都道府県)ごとに設定できます</li>
|
<li>事業所所在地(都道府県)ごとに設定できます</li>
|
||||||
<li>介護保険の適用年齢は設定可能です(デフォルト: 40歳以上)</li>
|
<li>介護保険の適用年齢は設定可能です(デフォルト: 40歳以上)</li>
|
||||||
<li><strong>年度適用期間:</strong> 「年度」に設定した値は <strong>その年の4月〜翌年3月</strong> に適用されます。<br>
|
<li>
|
||||||
例)2026年度設定 → 2026年4月〜2027年3月の計算に参照されます。<br>
|
<strong>年度適用期間:</strong> 「年度」に設定した値は
|
||||||
2026年1月〜3月分は 2025年度(rate_year=2025)の料率が参照されます。</li>
|
<strong>その年の4月〜翌年3月</strong> に適用されます。<br />
|
||||||
|
例)2026年度設定 → 2026年4月〜2027年3月の計算に参照されます。<br />
|
||||||
|
2026年1月〜3月分は
|
||||||
|
2025年度(rate_year=2025)の料率が参照されます。
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -807,20 +811,34 @@
|
|||||||
|
|
||||||
// メタデータに料率がない場合、データから逆算する
|
// メタデータに料率がない場合、データから逆算する
|
||||||
if (rates.healthNoCareRate == null) {
|
if (rates.healthNoCareRate == null) {
|
||||||
const refRow = data.find(r => Number(r.monthly_amount) > 0 && Number(r.health_insurance_no_care) > 0);
|
const refRow = data.find(
|
||||||
|
(r) =>
|
||||||
|
Number(r.monthly_amount) > 0 &&
|
||||||
|
Number(r.health_insurance_no_care) > 0,
|
||||||
|
);
|
||||||
if (refRow) {
|
if (refRow) {
|
||||||
const ma = Number(refRow.monthly_amount);
|
const ma = Number(refRow.monthly_amount);
|
||||||
rates.healthNoCareRate = Number(refRow.health_insurance_no_care) / ma * 100;
|
rates.healthNoCareRate =
|
||||||
rates.healthWithCareRate = Number(refRow.health_insurance_with_care) / ma * 100;
|
(Number(refRow.health_insurance_no_care) / ma) * 100;
|
||||||
|
rates.healthWithCareRate =
|
||||||
|
(Number(refRow.health_insurance_with_care) / ma) * 100;
|
||||||
if (Number(refRow.child_support ?? 0) > 0) {
|
if (Number(refRow.child_support ?? 0) > 0) {
|
||||||
rates.childSupportRate = Number(refRow.child_support) / ma * 100;
|
rates.childSupportRate =
|
||||||
|
(Number(refRow.child_support) / ma) * 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 厚生年金は低等級(1-3)で0になるため、pension_insurance > 0 の行を別途検索
|
// 厚生年金は低等級(1-3)で0になるため、pension_insurance > 0 の行を別途検索
|
||||||
if (rates.pensionRate == null) {
|
if (rates.pensionRate == null) {
|
||||||
const pensionRefRow = data.find(r => Number(r.monthly_amount) > 0 && Number(r.pension_insurance ?? 0) > 0);
|
const pensionRefRow = data.find(
|
||||||
|
(r) =>
|
||||||
|
Number(r.monthly_amount) > 0 &&
|
||||||
|
Number(r.pension_insurance ?? 0) > 0,
|
||||||
|
);
|
||||||
if (pensionRefRow) {
|
if (pensionRefRow) {
|
||||||
rates.pensionRate = Number(pensionRefRow.pension_insurance) / Number(pensionRefRow.monthly_amount) * 100;
|
rates.pensionRate =
|
||||||
|
(Number(pensionRefRow.pension_insurance) /
|
||||||
|
Number(pensionRefRow.monthly_amount)) *
|
||||||
|
100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
112
split_bonus_from_salary.py
Normal file
112
split_bonus_from_salary.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
"""
|
||||||
|
賞与を給料手当から分離する処理:
|
||||||
|
1. 賞与の勘定科目 (821) を追加
|
||||||
|
2. JE:833 の給料手当行 (4,720,000) を
|
||||||
|
給料手当 420,000 + 賞与 4,300,000 に分離
|
||||||
|
"""
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
|
conn = psycopg2.connect(
|
||||||
|
host='192.168.0.61', port=55432,
|
||||||
|
dbname='njts_acct', user='njts_app', password='njts_app2025'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=== 変更前の確認 ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT jl.journal_line_id, a.account_code, a.account_name, jl.debit, jl.credit, jl.line_description
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
WHERE jl.journal_entry_id = 833
|
||||||
|
ORDER BY jl.journal_line_id
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for r in rows:
|
||||||
|
print(f" line_id:{r[0]} {r[1]} {r[2]:<15} 借:{r[3] or 0:>12,.0f} 貸:{r[4] or 0:>12,.0f} {r[5] or ''}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Step 1: 賞与 (821) の科目を追加(存在しない場合のみ)
|
||||||
|
cur.execute("SELECT account_id FROM accounts WHERE account_code = '821'")
|
||||||
|
existing = cur.fetchone()
|
||||||
|
if existing:
|
||||||
|
print(f"科目 821 は既に存在します (ID:{existing[0]})")
|
||||||
|
bonus_account_id = existing[0]
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO accounts (account_code, account_name, account_type)
|
||||||
|
VALUES ('821', '賞与', 'expense')
|
||||||
|
RETURNING account_id
|
||||||
|
""")
|
||||||
|
bonus_account_id = cur.fetchone()[0]
|
||||||
|
print(f"科目 821 賞与 を追加しました (ID:{bonus_account_id})")
|
||||||
|
|
||||||
|
# Step 2: 給料手当行 (line_id:2162) の金額を 4,720,000→420,000 に変更
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE journal_lines
|
||||||
|
SET debit = 420000,
|
||||||
|
line_description = NULL
|
||||||
|
WHERE journal_line_id = 2162
|
||||||
|
""")
|
||||||
|
print(f"給料手当行 (line_id:2162) を 4,720,000→420,000 に更新")
|
||||||
|
|
||||||
|
# Step 3: 賞与の新しい明細行を追加
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit, line_description)
|
||||||
|
VALUES (833, %s, 4300000, 0, '賞与')
|
||||||
|
RETURNING journal_line_id
|
||||||
|
""", (bonus_account_id,))
|
||||||
|
new_line_id = cur.fetchone()[0]
|
||||||
|
print(f"賞与行 (line_id:{new_line_id}) を追加: 4,300,000 円")
|
||||||
|
|
||||||
|
# Step 4: 仕訳の摘要も更新
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE journal_entries
|
||||||
|
SET description = '張翔鶴 10月給料・賞与'
|
||||||
|
WHERE journal_entry_id = 833
|
||||||
|
""")
|
||||||
|
print("仕訳摘要を更新: '張翔鶴 10月給料 + ボーナス' → '張翔鶴 10月給料・賞与'")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 変更後の確認 ===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT jl.journal_line_id, a.account_code, a.account_name, jl.debit, jl.credit, jl.line_description
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
WHERE jl.journal_entry_id = 833
|
||||||
|
ORDER BY jl.journal_line_id
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
debit_total = 0
|
||||||
|
credit_total = 0
|
||||||
|
for r in rows:
|
||||||
|
d = r[3] or 0
|
||||||
|
c = r[4] or 0
|
||||||
|
debit_total += d
|
||||||
|
credit_total += c
|
||||||
|
print(f" line_id:{r[0]} {r[1]} {r[2]:<15} 借:{d:>12,.0f} 貸:{c:>12,.0f} {r[5] or ''}")
|
||||||
|
print(f" {'合計':<30} 借:{debit_total:>12,.0f} 貸:{credit_total:>12,.0f}")
|
||||||
|
print(f" 貸借バランス: {'✓ OK' if debit_total == credit_total else '✗ NG!'}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=== 期間合計(給料手当・賞与)===")
|
||||||
|
cur.execute("""
|
||||||
|
SELECT a.account_code, a.account_name, SUM(jl.debit) AS total
|
||||||
|
FROM journal_lines jl
|
||||||
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
|
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
|
||||||
|
WHERE je.is_latest = true AND je.is_deleted = false
|
||||||
|
AND je.entry_date >= '2025-06-01' AND je.entry_date <= '2025-12-31'
|
||||||
|
AND a.account_code IN ('812', '813', '821')
|
||||||
|
GROUP BY a.account_code, a.account_name
|
||||||
|
ORDER BY a.account_code
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for r in rows:
|
||||||
|
print(f" {r[0]} {r[1]:<12}: {r[2] or 0:>12,.0f} 円")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print()
|
||||||
|
print("完了しました。")
|
||||||
Reference in New Issue
Block a user