Files
njts-accounting-core/frontend/js/journal-entry.js
FNOS-WIN11ENT\choshoukaku 88163423e0 修正
2025-12-15 17:57:49 +09:00

173 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// === 仕訳登録 画面用ユーティリティ ===
// 税率を permilで表現10%→100‰, 8%→80‰
// 小数誤差を避けるため、整数演算ベースで処理
const TAX_PERMIL = {
"10": 100, // 10%
"8": 80 // 8%
};
// 補助: 四捨五入(整数)
function roundInt(x) {
return Math.round(x);
}
// 補助: 税抜 = round( 税込 × 1000 / (1000 + ratePermil) )
function calcExclFromIncl(inclAmount, ratePermil) {
const denom = 1000 + ratePermil;
return roundInt((inclAmount * 1000) / denom);
}
// 補助: 税額 = 税込 税抜(税込入力時)
function calcTaxFromIncl(inclAmount, ratePermil) {
const excl = calcExclFromIncl(inclAmount, ratePermil);
return inclAmount - excl;
}
// 補助: 税額 = round( 税抜 × ratePermil / 1000 )(税抜入力時)
function calcTaxFromExcl(exclAmount, ratePermil) {
return roundInt((exclAmount * ratePermil) / 1000);
}
// 仕訳行タイプ判定(会計実務ルール)
function isForceInclAccount(accountCodeOrName) {
// 売掛金/買掛金/未収入金/未払金 は税込固定
const keywords = ["売掛金", "買掛金", "未収入金", "未払金"];
return keywords.some(k => accountCodeOrName.includes(k));
}
// 税方向→仮受(売上) or 仮払(仕入)
function taxAccountNameByDirection(direction) {
return direction === "仮受" ? "仮受消費税等" : "仮払消費税等";
}
// 状態管理(税額手修正フラグ)
let manualTaxOverridden = false;
// UIイベント: 税額手修正が行われたら自動連動解除
function onTaxAmountEdited() {
manualTaxOverridden = true;
const badge = document.querySelector("#taxManualBadge");
if (badge) badge.style.display = "inline-block";
}
// メイン: 画面上の入力からプレビュー行を生成複数税率もOK
function buildJournalPreview({
lines, // [{accountId, accountName, dc, amount, taxRate, taxApplicable, subAccountId}]
taxMode, // "税込" | "税抜"
taxDirection // "仮受" | "仮払"
}) {
// 税率ごとに集計(補助科目単位で集約)
const buckets = new Map(); // key = `${rate}-${subAccountId || ''}-${taxDirection}`
let debitTotal = 0;
let creditTotal = 0;
const normalLines = []; // 税対象外や通常科目
for (const ln of lines) {
const isForceIncl = isForceInclAccount(ln.accountName);
const mode = isForceIncl ? "税込" : taxMode; // 強制税込
if (ln.dc === "D") debitTotal += ln.amount;
else creditTotal += ln.amount;
normalLines.push(ln);
if (!ln.taxApplicable || !ln.taxRate) continue; // 対象外は課税計算しない
const ratePermil = TAX_PERMIL[String(ln.taxRate)];
if (ratePermil == null) continue;
// 税額の計算(自動計算のみ。手修正済ならここで足さない)
if (!manualTaxOverridden) {
let taxAmount = 0;
if (mode === "税込") {
// 税抜 = round( 税込 ÷(1+税率) ) → 税額=差額
const excl = calcExclFromIncl(ln.amount, ratePermil);
taxAmount = ln.amount - excl;
} else {
// 税抜 → 税額 = round( 税抜 × 税率 )
taxAmount = calcTaxFromExcl(ln.amount, ratePermil);
}
const key = `${ln.taxRate}-${ln.subAccountId || ""}-${taxDirection}`;
const prev = buckets.get(key) || 0;
buckets.set(key, prev + taxAmount);
}
}
// 税行を作成税率・補助科目ごと・税方向ごとに1行
const taxLines = [];
for (const [key, amt] of buckets.entries()) {
const [rate, subAccountId, direction] = key.split("-");
if (amt === 0) continue;
const taxAccount = taxAccountNameByDirection(direction);
// 仮受は「貸方」、仮払は「借方」
const dc = direction === "仮受" ? "C" : "D";
taxLines.push({
accountName: taxAccount,
dc,
amount: amt,
taxRate: Number(rate),
taxApplicable: true,
subAccountId: subAccountId || null,
locked: true, // 科目/税区分/方向ロック
amountEditable: true // 金額のみ編集可
});
if (dc === "D") debitTotal += amt; else creditTotal += amt;
}
// プレビュー結果
return {
lines: [...normalLines, ...taxLines],
debitTotal,
creditTotal,
balanced: debitTotal === creditTotal
};
}
// 登録ボタン押下時サーバーに渡すDTOを組み立て
function buildPostPayload(uiState) {
const preview = buildJournalPreview(uiState);
// バリデーション:貸借一致
if (!preview.balanced && !manualTaxOverridden) {
throw new Error("貸借が一致していません。税額手修正が必要な場合は、税額を調整して再試行してください。");
}
// 税行が必要なのに無い場合はエラー(対象外以外で税率がある)
const needTax = uiState.lines.some(l => l.taxApplicable && l.taxRate);
const hasTaxLine = preview.lines.some(l => l.accountName === "仮受消費税等" || l.accountName === "仮払消費税等");
if (needTax && !hasTaxLine && !manualTaxOverridden) {
throw new Error("課税対象ですが消費税行がありません。");
}
return {
taxMode: uiState.taxMode, // "税込" | "税抜"
taxDirection: uiState.taxDirection, // "仮受" | "仮払"
manualTaxOverridden, // trueなら後端は自動再計算しない
lines: preview.lines.map(l => ({
accountId: l.accountId,
accountName: l.accountName,
dc: l.dc, // "D" | "C"
amount: l.amount, // 円(整数)
taxApplicable: !!l.taxApplicable,
taxRate: l.taxRate || null, // 8/10/null
subAccountId: l.subAccountId || null,
isTaxLine: (l.accountName === "仮受消費税等" || l.accountName === "仮払消費税等"),
lockAccountMeta: !!l.locked,
amountEditable: !!l.amountEditable
}))
};
}
// 画面初期化で税額手修正バッジ非表示
document.addEventListener("DOMContentLoaded", () => {
const badge = document.querySelector("#taxManualBadge");
if (badge) badge.style.display = "none";
});