修正
This commit is contained in:
@@ -129,6 +129,7 @@
|
||||
<th style="width: 220px">科目</th>
|
||||
<th style="width: 120px">借方</th>
|
||||
<th style="width: 120px">貸方</th>
|
||||
<th style="width: 90px">税率</th>
|
||||
<th style="width: 180px">摘要(行)</th>
|
||||
<th style="width: 60px">操作</th>
|
||||
</tr>
|
||||
@@ -141,6 +142,7 @@
|
||||
<th id="creditTotal" class="right">0</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
@@ -159,6 +161,43 @@
|
||||
const API = "";
|
||||
let accounts = [];
|
||||
|
||||
// カスタムアラートモーダル
|
||||
function showAlert(msg, type, callback) {
|
||||
const configs = {
|
||||
success: {
|
||||
icon: "✔",
|
||||
iconColor: "#22a84b",
|
||||
bg: "#f0faf4",
|
||||
borderTop: "#22a84b",
|
||||
},
|
||||
error: {
|
||||
icon: "✖",
|
||||
iconColor: "#e53935",
|
||||
bg: "#fff5f5",
|
||||
borderTop: "#e53935",
|
||||
},
|
||||
warning: {
|
||||
icon: "⚠",
|
||||
iconColor: "#e67e00",
|
||||
bg: "#fffbf0",
|
||||
borderTop: "#f0a500",
|
||||
},
|
||||
};
|
||||
const c = configs[type] || configs.warning;
|
||||
const overlay = document.getElementById("alertModalOverlay");
|
||||
document.getElementById("alertModalIcon").innerHTML =
|
||||
`<span style="font-size:52px;line-height:1;color:${c.iconColor};">${c.icon}</span>`;
|
||||
document.getElementById("alertModalMsg").textContent = msg;
|
||||
const box = document.getElementById("alertModalBox");
|
||||
box.style.borderTop = `5px solid ${c.borderTop}`;
|
||||
box.style.background = c.bg;
|
||||
overlay.style.display = "flex";
|
||||
document.getElementById("alertModalOkBtn").onclick = function () {
|
||||
overlay.style.display = "none";
|
||||
if (callback) callback();
|
||||
};
|
||||
}
|
||||
|
||||
// 科目グループ定義(仕訳入力と同期)
|
||||
const ACCOUNT_GROUPS = [
|
||||
// 資産の部
|
||||
@@ -438,6 +477,14 @@
|
||||
</td>
|
||||
<td><input type="number" min="0" style="text-align:right"></td>
|
||||
<td><input type="number" min="0" style="text-align:right"></td>
|
||||
<td>
|
||||
<select class="tax-rate-select" style="width:100%;padding:4px;">
|
||||
<option value="">—</option>
|
||||
<option value="10">10%</option>
|
||||
<option value="8">8%(軽減)</option>
|
||||
<option value="0">0%(非課税)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="line-memo-input" placeholder="行摘要(任意)"></td>
|
||||
<td><button onclick="removeRow(this)">削除</button></td>
|
||||
`;
|
||||
@@ -464,6 +511,11 @@
|
||||
const creditVal = Number(line.credit) || 0;
|
||||
numberInputs[0].value = debitVal > 0 ? String(debitVal) : "";
|
||||
numberInputs[1].value = creditVal > 0 ? String(creditVal) : "";
|
||||
// 税率を設定
|
||||
if (line.tax_rate != null) {
|
||||
const sel = tr.querySelector(".tax-rate-select");
|
||||
sel.value = String(line.tax_rate);
|
||||
}
|
||||
// 摘要(行)を設定
|
||||
const memoInput = tr.querySelector(".line-memo-input");
|
||||
if (memoInput) memoInput.value = line.line_description || "";
|
||||
@@ -556,11 +608,17 @@
|
||||
const credit = Number(numberInputs[1]?.value || 0);
|
||||
const lineDesc =
|
||||
tr.querySelector(".line-memo-input")?.value.trim() || undefined;
|
||||
const taxRateRaw = tr.querySelector(".tax-rate-select")?.value;
|
||||
const taxRate =
|
||||
taxRateRaw !== "" && taxRateRaw != null
|
||||
? Number(taxRateRaw)
|
||||
: undefined;
|
||||
if (debit === 0 && credit === 0) return;
|
||||
lines.push({
|
||||
account_id: accountId,
|
||||
debit,
|
||||
credit,
|
||||
...(taxRate !== undefined ? { tax_rate: taxRate } : {}),
|
||||
...(lineDesc ? { line_description: lineDesc } : {}),
|
||||
});
|
||||
d += debit;
|
||||
@@ -599,15 +657,17 @@
|
||||
if (!res.ok) return showError(data.detail || "登録失敗");
|
||||
|
||||
// 登録成功 - 親窓口の検索を再実行してから画面を閉じる
|
||||
alert(`登録しました(ID: ${data.journal_entry_id})`);
|
||||
localStorage.removeItem("editSource");
|
||||
|
||||
// 親窓口(列表页)の searchJournals()を呼び出す
|
||||
if (window.opener && window.opener.searchJournals) {
|
||||
window.opener.searchJournals();
|
||||
}
|
||||
|
||||
window.close();
|
||||
showAlert(
|
||||
`登録しました(ID: ${data.journal_entry_id})`,
|
||||
"success",
|
||||
() => {
|
||||
localStorage.removeItem("editSource");
|
||||
if (window.opener && window.opener.searchJournals) {
|
||||
window.opener.searchJournals();
|
||||
}
|
||||
window.close();
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
showError("通信エラー");
|
||||
}
|
||||
@@ -629,5 +689,57 @@
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
<!-- アラートモーダル -->
|
||||
<div
|
||||
id="alertModalOverlay"
|
||||
style="
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
z-index: 9999;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
"
|
||||
>
|
||||
<div
|
||||
id="alertModalBox"
|
||||
style="
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 32px 36px;
|
||||
min-width: 320px;
|
||||
max-width: 480px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
||||
text-align: center;
|
||||
"
|
||||
>
|
||||
<div id="alertModalIcon" style="margin-bottom: 14px"></div>
|
||||
<div
|
||||
id="alertModalMsg"
|
||||
style="
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
margin-bottom: 26px;
|
||||
white-space: pre-wrap;
|
||||
"
|
||||
></div>
|
||||
<button
|
||||
id="alertModalOkBtn"
|
||||
style="
|
||||
padding: 9px 36px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
background: #4a90d9;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -129,7 +129,7 @@
|
||||
}
|
||||
@media print {
|
||||
@page {
|
||||
margin: 20mm 20mm 25mm 20mm;
|
||||
margin: 5mm 15mm 20mm 15mm;
|
||||
@bottom-center {
|
||||
content: "ページ " counter(page);
|
||||
font-size: 0.75em;
|
||||
@@ -869,6 +869,40 @@
|
||||
<script>
|
||||
const API = "";
|
||||
|
||||
// カスタム通知トースト
|
||||
function showAlert(msg, type) {
|
||||
const configs = {
|
||||
success: {
|
||||
icon: "✔",
|
||||
iconColor: "#22a84b",
|
||||
bg: "#f0faf4",
|
||||
border: "#22a84b",
|
||||
},
|
||||
error: {
|
||||
icon: "✖",
|
||||
iconColor: "#e53935",
|
||||
bg: "#fff5f5",
|
||||
border: "#e53935",
|
||||
},
|
||||
warning: {
|
||||
icon: "⚠",
|
||||
iconColor: "#e67e00",
|
||||
bg: "#fffbf0",
|
||||
border: "#f0a500",
|
||||
},
|
||||
};
|
||||
const c = configs[type] || configs.warning;
|
||||
const container = document.getElementById("alertToastContainer");
|
||||
const toast = document.createElement("div");
|
||||
toast.style.cssText = `pointer-events:auto;display:flex;align-items:flex-start;gap:12px;background:${c.bg};border:1px solid ${c.border};border-left:5px solid ${c.border};border-radius:8px;padding:14px 16px;min-width:280px;max-width:420px;box-shadow:0 4px 16px rgba(0,0,0,0.14);animation:alertSlideIn 0.25s ease;`;
|
||||
const msgEsc = String(msg).replace(/&/g, "&").replace(/</g, "<");
|
||||
toast.innerHTML = `<span style="font-size:26px;color:${c.iconColor};line-height:1;flex-shrink:0;">${c.icon}</span><span style="font-size:14px;color:#333;flex:1;white-space:pre-wrap;margin-top:3px;">${msgEsc}</span><button onclick="this.parentElement.remove()" style="background:none;border:none;cursor:pointer;color:#aaa;font-size:20px;line-height:1;padding:0 0 0 8px;flex-shrink:0;">×</button>`;
|
||||
container.appendChild(toast);
|
||||
setTimeout(() => {
|
||||
if (toast.parentElement) toast.remove();
|
||||
}, 4500);
|
||||
}
|
||||
|
||||
// 千分位カンマフォーマット関数
|
||||
function formatNumberInput(input) {
|
||||
let value = input.value.replace(/,/g, ""); // 既存のカンマを削除
|
||||
@@ -1142,7 +1176,7 @@
|
||||
const res = await fetch(`${API}/accounts`);
|
||||
if (!res.ok) {
|
||||
console.error("❌ API 错误:", res.status);
|
||||
alert("科目一覧の取得に失敗しました");
|
||||
showAlert("科目一覧の取得に失敗しました", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1796,17 +1830,18 @@
|
||||
.value.trim();
|
||||
|
||||
if (dateInput.validity.badInput) {
|
||||
alert(
|
||||
showAlert(
|
||||
"日付が正しくありません。存在しない日付が入力されています(例:11月31日は無効です)",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!entryDate) {
|
||||
alert("仕訳日を入力してください");
|
||||
showAlert("仕訳日を入力してください", "warning");
|
||||
return;
|
||||
}
|
||||
if (!description) {
|
||||
alert("摘要を入力してください");
|
||||
showAlert("摘要を入力してください", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1824,12 +1859,15 @@
|
||||
if (debit === 0 && credit === 0) return; // 空行スキップ
|
||||
|
||||
if (!accountId) {
|
||||
alert("科目が選択されていない行があります");
|
||||
showAlert("科目が選択されていない行があります", "warning");
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
if (debit > 0 && credit > 0) {
|
||||
alert("1行に借方と貸方の両方を入力することはできません");
|
||||
showAlert(
|
||||
"1行に借方と貸方の両方を入力することはできません",
|
||||
"warning",
|
||||
);
|
||||
valid = false;
|
||||
return;
|
||||
}
|
||||
@@ -1848,12 +1886,13 @@
|
||||
|
||||
if (!valid) return;
|
||||
if (lines.length < 2) {
|
||||
alert("仕訳明細は2行以上必要です");
|
||||
showAlert("仕訳明細は2行以上必要です", "warning");
|
||||
return;
|
||||
}
|
||||
if (debitSum !== creditSum) {
|
||||
alert(
|
||||
showAlert(
|
||||
`貸借が一致していません(差額:${Math.abs(debitSum - creditSum).toLocaleString()} 円)`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1883,7 +1922,7 @@
|
||||
}
|
||||
|
||||
saveDescriptionToHistory(description);
|
||||
alert("仕訳を登録しました");
|
||||
showAlert("仕訳を登録しました", "success");
|
||||
|
||||
const keep = document.getElementById("detailKeepAfterSubmit").checked;
|
||||
if (!keep) {
|
||||
@@ -1893,7 +1932,7 @@
|
||||
|
||||
searchJournals();
|
||||
} catch (e) {
|
||||
alert("登録エラー: " + e.message);
|
||||
showAlert("登録エラー: " + e.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1922,13 +1961,14 @@
|
||||
).value;
|
||||
|
||||
if (entryDateInput.validity.badInput) {
|
||||
alert(
|
||||
showAlert(
|
||||
"日付が正しくありません。存在しない日付が入力されています(例:11月31日は無効です)",
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!entryDate) {
|
||||
alert("仕訳日を入力してください");
|
||||
showAlert("仕訳日を入力してください", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1948,15 +1988,15 @@
|
||||
);
|
||||
|
||||
if (!accountId) {
|
||||
alert("科目を選択してください");
|
||||
showAlert("科目を選択してください", "warning");
|
||||
return;
|
||||
}
|
||||
if (!methodId) {
|
||||
alert("取引手段を選択してください");
|
||||
showAlert("取引手段を選択してください", "warning");
|
||||
return;
|
||||
}
|
||||
if (amount <= 0) {
|
||||
alert("金額を入力してください");
|
||||
showAlert("金額を入力してください", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1978,6 +2018,7 @@
|
||||
account_id: accountId,
|
||||
debit: netAmount,
|
||||
credit: 0,
|
||||
...(taxRateNum !== null ? { tax_rate: taxRateNum } : {}),
|
||||
});
|
||||
|
||||
// 借方: 仮払消費税等(税額)※税額がある場合のみ
|
||||
@@ -1985,7 +2026,7 @@
|
||||
const taxPaidAcc =
|
||||
taxPaidAccounts.length > 0 ? taxPaidAccounts[0] : null;
|
||||
if (!taxPaidAcc) {
|
||||
alert("仮払消費税等の勘定科目が見つかりません");
|
||||
showAlert("仮払消費税等の勘定科目が見つかりません", "error");
|
||||
return;
|
||||
}
|
||||
lines.push({
|
||||
@@ -2015,6 +2056,7 @@
|
||||
account_id: accountId,
|
||||
debit: 0,
|
||||
credit: netAmount,
|
||||
...(taxRateNum !== null ? { tax_rate: taxRateNum } : {}),
|
||||
});
|
||||
|
||||
// 貸方: 仮受消費税等(税額)※税額がある場合のみ
|
||||
@@ -2022,7 +2064,7 @@
|
||||
const taxReceivedAcc =
|
||||
taxReceivedAccounts.length > 0 ? taxReceivedAccounts[0] : null;
|
||||
if (!taxReceivedAcc) {
|
||||
alert("仮受消費税等の勘定科目が見つかりません");
|
||||
showAlert("仮受消費税等の勘定科目が見つかりません", "error");
|
||||
return;
|
||||
}
|
||||
lines.push({
|
||||
@@ -2060,7 +2102,7 @@
|
||||
const errorMsg =
|
||||
errorData.detail || `登録に失敗しました (HTTP ${res.status})`;
|
||||
console.error("Error:", errorData);
|
||||
alert(errorMsg);
|
||||
showAlert(errorMsg, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2069,7 +2111,7 @@
|
||||
// 摘要を履歴に保存
|
||||
saveDescriptionToHistory(description);
|
||||
|
||||
alert(`登録完了(ID=${result.journal_entry_id})`);
|
||||
showAlert(`登録完了(ID=${result.journal_entry_id})`, "success");
|
||||
|
||||
// 「登録後に明細を保持する」チェックボックスを確認
|
||||
const keepDetails = document.getElementById(
|
||||
@@ -2092,8 +2134,9 @@
|
||||
searchJournals();
|
||||
} catch (error) {
|
||||
console.error("送信エラー:", error);
|
||||
alert(
|
||||
showAlert(
|
||||
`エラーが発生しました: ${error.message}\n\nサーバーが起動しているか確認してください。`,
|
||||
"error",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2387,6 +2430,16 @@
|
||||
const conditionsText =
|
||||
conditions.length > 0 ? conditions.join(" | ") : "検索条件: すべて";
|
||||
|
||||
// 印刷日時
|
||||
const printedAt = new Date().toLocaleString("ja-JP", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
|
||||
// 月ごとにテーブルを生成(印刷用)
|
||||
Object.keys(monthGroups)
|
||||
.sort()
|
||||
@@ -2409,11 +2462,14 @@
|
||||
<table style="margin-bottom: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="6" style="text-align: center; font-size: 1.2em; padding: 8px; border: none; background: transparent;">仕訳検索結果</th>
|
||||
<th colspan="5" style="text-align: center; font-size: 1.2em; padding: 8px; border: none; background: transparent;">仕訳検索結果</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="5" style="text-align: right; font-weight: normal; font-size: 0.85em; padding: 2px 4px; border: none; background: transparent;">印刷日時: ${printedAt}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="3" style="text-align: left; font-weight: normal; font-size: 0.9em; padding: 4px; border: none; background: transparent;">${conditionsText}</th>
|
||||
<th colspan="3" style="text-align: right; font-weight: normal; font-size: 0.9em; padding: 4px; border: none; background: transparent;">${monthDisplay}</th>
|
||||
<th colspan="2" style="text-align: right; font-weight: normal; font-size: 0.9em; padding: 4px; border: none; background: transparent;">${monthDisplay}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="white-space: nowrap;">日付</th>
|
||||
@@ -2421,7 +2477,6 @@
|
||||
<th style="white-space: nowrap;">借方合計</th>
|
||||
<th style="white-space: nowrap;">貸方合計</th>
|
||||
<th>税率</th>
|
||||
<th style="white-space: nowrap;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@@ -2452,20 +2507,6 @@
|
||||
<td style="text-align:center">${
|
||||
e.tax_rates ? e.tax_rates + "%" : ""
|
||||
}</td>
|
||||
<td style="white-space: nowrap;">
|
||||
<button onclick="viewJournal(${
|
||||
e.journal_entry_id
|
||||
})">表示</button>
|
||||
<button onclick="copyJournal(${
|
||||
e.journal_entry_id
|
||||
})" style="color: green;">複製</button>
|
||||
<button onclick="reverseAndEdit(${
|
||||
e.journal_entry_id
|
||||
})">修正</button>
|
||||
<button onclick="deleteJournalEntry(${
|
||||
e.journal_entry_id
|
||||
})" style="color: red;">削除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
@@ -2478,7 +2519,7 @@
|
||||
<td colspan="2" style="text-align:right; white-space: nowrap;">合計:</td>
|
||||
<td style="text-align:right; white-space: nowrap;">${monthDebitTotal.toLocaleString()}</td>
|
||||
<td style="text-align:right; white-space: nowrap;">${monthCreditTotal.toLocaleString()}</td>
|
||||
<td colspan="2"></td>
|
||||
<td></td>
|
||||
`;
|
||||
tbody.appendChild(totalRow);
|
||||
|
||||
@@ -2502,7 +2543,7 @@
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
alert(`検索に失敗しました: ${err.message}`);
|
||||
showAlert(`検索に失敗しました: ${err.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2722,7 +2763,7 @@
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
alert(`検索に失敗しました: ${err.message}`);
|
||||
showAlert(`検索に失敗しました: ${err.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2737,7 +2778,7 @@
|
||||
try {
|
||||
const res = await fetch(`${API}/journal-entries/${id}`);
|
||||
if (!res.ok) {
|
||||
alert("仕訳の取得に失敗しました");
|
||||
showAlert("仕訳の取得に失敗しました", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2916,14 +2957,13 @@
|
||||
const dateInput = document.getElementById("entryDate");
|
||||
dateInput.focus();
|
||||
|
||||
alert(
|
||||
`仕訳を複製しました。\n\n` +
|
||||
`※ このコピーは新規仕訳として独立した記録になります。\n` +
|
||||
`※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||
showAlert(
|
||||
`仕訳を複製しました。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||
"success",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert(`エラーが発生しました: ${error.message}`);
|
||||
showAlert(`エラーが発生しました: ${error.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2954,7 +2994,7 @@
|
||||
async function deleteJournalEntry(id) {
|
||||
const reason = prompt("削除理由を入力してください:");
|
||||
if (!reason || reason.trim() === "") {
|
||||
alert("削除理由の入力が必要です");
|
||||
showAlert("削除理由の入力が必要です", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2970,11 +3010,11 @@
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
alert(data.detail || "削除に失敗しました");
|
||||
showAlert(data.detail || "削除に失敗しました", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
alert("削除しました");
|
||||
showAlert("削除しました", "success");
|
||||
searchJournals(); // リストを再読み込み
|
||||
}
|
||||
|
||||
@@ -3056,5 +3096,30 @@
|
||||
await searchJournals();
|
||||
});
|
||||
</script>
|
||||
<div
|
||||
id="alertToastContainer"
|
||||
style="
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
pointer-events: none;
|
||||
"
|
||||
></div>
|
||||
<style>
|
||||
@keyframes alertSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(40px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<th>科目名</th>
|
||||
<th>借方</th>
|
||||
<th>貸方</th>
|
||||
<th>税率</th>
|
||||
<th>摘要(行)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -124,6 +125,7 @@
|
||||
<td style="text-align:right">${
|
||||
line.credit > 0 ? line.credit.toLocaleString() : ""
|
||||
}</td>
|
||||
<td style="text-align:center">${line.tax_rate != null ? line.tax_rate + "%" : ""}</td>
|
||||
<td>${line.line_description || ""}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
@@ -140,6 +142,7 @@
|
||||
<td style="text-align:right">${totalDebit.toLocaleString()}</td>
|
||||
<td style="text-align:right">${totalCredit.toLocaleString()}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
`;
|
||||
tbody.appendChild(totalRow);
|
||||
}
|
||||
|
||||
@@ -195,14 +195,22 @@
|
||||
<div id="tab-salary" class="tab-content-calc active">
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年月:
|
||||
対象年:
|
||||
<input
|
||||
type="month"
|
||||
id="filterYearMonth"
|
||||
style="width: 160px"
|
||||
placeholder="YYYY-MM"
|
||||
type="number"
|
||||
id="filterYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
従業員:
|
||||
<select id="filterEmployee" style="min-width: 200px">
|
||||
<option value="">全員</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button>
|
||||
<button class="btn btn-success" onclick="showCalculateForm()">
|
||||
新規計算
|
||||
@@ -373,6 +381,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>計算オプション</h3>
|
||||
<div style="display: flex; gap: 30px; padding: 8px 0">
|
||||
<label
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
<input type="checkbox" id="calcIncomeTax" checked />
|
||||
所得税を計算する
|
||||
</label>
|
||||
<label
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
<input type="checkbox" id="calcSocialInsurance" checked />
|
||||
社会保険を計算する
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">計算実行</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -566,11 +602,66 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>開始年月 *</label>
|
||||
<input type="month" id="voucherStartYearMonth" required />
|
||||
<div style="display: flex; gap: 6px; align-items: center">
|
||||
<input
|
||||
type="text"
|
||||
id="voucherStartYear"
|
||||
style="width: 90px"
|
||||
placeholder="年"
|
||||
maxlength="4"
|
||||
pattern="[0-9]{4}"
|
||||
inputmode="numeric"
|
||||
required
|
||||
/>
|
||||
<span style="color: #666">年</span>
|
||||
<select id="voucherStartMonth" required>
|
||||
<option value="">月</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
</select>
|
||||
<span style="color: #666">月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>終了年月</label>
|
||||
<input type="month" id="voucherEndYearMonth" />
|
||||
<div style="display: flex; gap: 6px; align-items: center">
|
||||
<input
|
||||
type="text"
|
||||
id="voucherEndYear"
|
||||
style="width: 90px"
|
||||
placeholder="年"
|
||||
maxlength="4"
|
||||
pattern="[0-9]{4}"
|
||||
inputmode="numeric"
|
||||
/>
|
||||
<span style="color: #666">年</span>
|
||||
<select id="voucherEndMonth">
|
||||
<option value="">月</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
</select>
|
||||
<span style="color: #666">月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="color: #999; font-size: 12px">
|
||||
@@ -799,24 +890,41 @@
|
||||
const bonusSelect = document.getElementById("bonusEmployeeSelect");
|
||||
if (bonusSelect) bonusSelect.innerHTML = optionsHtml;
|
||||
|
||||
// 検索用フィルタのドロップダウンも更新
|
||||
const filterSelect = document.getElementById("filterEmployee");
|
||||
if (filterSelect) {
|
||||
const currentVal = filterSelect.value;
|
||||
filterSelect.innerHTML =
|
||||
'<option value="">全員</option>' +
|
||||
employees
|
||||
.map(
|
||||
(emp) =>
|
||||
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`,
|
||||
)
|
||||
.join("");
|
||||
filterSelect.value = currentVal;
|
||||
}
|
||||
|
||||
return employees;
|
||||
}
|
||||
|
||||
async function loadPayrolls() {
|
||||
const ym = document.getElementById("filterYearMonth").value;
|
||||
let year = null;
|
||||
let month = null;
|
||||
if (ym) {
|
||||
const parts = ym.split("-");
|
||||
if (parts.length === 2) {
|
||||
year = Number(parts[0]);
|
||||
month = Number(parts[1]);
|
||||
}
|
||||
// 前回の給与明細をクリア
|
||||
const detailDiv = document.getElementById("payrollDetail");
|
||||
if (detailDiv) {
|
||||
detailDiv.style.display = "none";
|
||||
detailDiv.innerHTML = "";
|
||||
}
|
||||
|
||||
const year =
|
||||
Number(document.getElementById("filterYear")?.value) || null;
|
||||
const employeeId =
|
||||
document.getElementById("filterEmployee")?.value || null;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (year) params.append("payroll_year", year);
|
||||
if (month) params.append("payroll_month", month);
|
||||
if (employeeId) params.append("employee_id", employeeId);
|
||||
|
||||
// employeeMap が空なら読み込む
|
||||
if (!employeeMap || Object.keys(employeeMap).length === 0) {
|
||||
await loadEmployees();
|
||||
@@ -827,52 +935,66 @@
|
||||
`${API_BASE}/payroll/calculation/?${params}`,
|
||||
);
|
||||
const payrolls = await response.json();
|
||||
const html = payrolls
|
||||
.map((p) => {
|
||||
const emp = employeeMap[p.employee_id];
|
||||
const empLabel = emp
|
||||
? `従業員ID: ${p.employee_id} | ${emp.employee_code} - ${emp.name}`
|
||||
: `従業員ID: ${p.employee_id}`;
|
||||
|
||||
if (!payrolls || payrolls.length === 0) {
|
||||
document.getElementById("payrollList").innerHTML =
|
||||
"<p>給与データがありません</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// 月別にグループ化
|
||||
const byMonth = {};
|
||||
payrolls.forEach((p) => {
|
||||
const key = `${p.payroll_year}-${String(p.payroll_month).padStart(2, "0")}`;
|
||||
if (!byMonth[key]) byMonth[key] = [];
|
||||
byMonth[key].push(p);
|
||||
});
|
||||
|
||||
// 月順にソート
|
||||
const sortedMonths = Object.keys(byMonth).sort();
|
||||
|
||||
const html = sortedMonths
|
||||
.map((monthKey) => {
|
||||
const [y, m] = monthKey.split("-");
|
||||
const items = byMonth[monthKey]
|
||||
.map((p) => {
|
||||
const emp = employeeMap[p.employee_id];
|
||||
const empLabel = emp
|
||||
? `${emp.employee_code} - ${emp.name}`
|
||||
: `従業員ID: ${p.employee_id}`;
|
||||
return `
|
||||
<div class="payroll-item" style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||
<div style="flex:1; cursor:pointer;" onclick="viewPayroll(${p.payroll_id})">
|
||||
<div>
|
||||
<strong>${empLabel}</strong>
|
||||
<span class="status-badge status-${p.status}">${getStatusLabel(p.status)}</span>
|
||||
<span style="margin-left:8px; font-size:12px; color:#666;">支給日: ${p.payment_date}</span>
|
||||
</div>
|
||||
<div style="margin-top:4px;">
|
||||
<strong>差引支給額: ¥${Number(p.net_payment).toLocaleString()}</strong>
|
||||
<span style="color:#666; font-size:13px;">(総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()})</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-small" onclick="deletePayroll(${p.payroll_id}); event.stopPropagation();" style="margin-left:10px; white-space:nowrap; background-color:#dc3545; color:#fff; border:none;">
|
||||
削除
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<div class="payroll-item" style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div style="flex: 1; cursor: pointer;" onclick="viewPayroll(${
|
||||
p.payroll_id
|
||||
})">
|
||||
<div>
|
||||
<strong>${p.payroll_year}年${
|
||||
p.payroll_month
|
||||
}月</strong>
|
||||
<span class="status-badge status-${
|
||||
p.status
|
||||
}">${getStatusLabel(p.status)}</span>
|
||||
</div>
|
||||
<div>
|
||||
${empLabel} | 支給日: ${p.payment_date}
|
||||
</div>
|
||||
<div>
|
||||
<strong>差引支給額: ¥${Number(
|
||||
p.net_payment,
|
||||
).toLocaleString()}</strong>
|
||||
(総支給: ¥${Number(
|
||||
p.total_payment,
|
||||
).toLocaleString()} - 控除: ¥${Number(
|
||||
p.total_deduction,
|
||||
).toLocaleString()})
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-small" onclick="deletePayroll(${
|
||||
p.payroll_id
|
||||
}); event.stopPropagation();" style="margin-left: 10px; white-space: nowrap; background-color: #dc3545; color: #fff; border: none;">
|
||||
削除
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
<div style="margin-bottom:20px;">
|
||||
<div style="background:#e8f0fe; border-left:4px solid #007bff; padding:8px 14px; font-weight:bold; font-size:15px; margin-bottom:6px;">
|
||||
${Number(y)}年 ${Number(m)}月
|
||||
</div>
|
||||
${items}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
document.getElementById("payrollList").innerHTML =
|
||||
html || "<p>給与データがありません</p>";
|
||||
document.getElementById("payrollList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("給与一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
@@ -927,6 +1049,11 @@
|
||||
// ensure payroll_year/month are numbers
|
||||
data.payroll_year = valid.year;
|
||||
data.payroll_month = valid.month;
|
||||
// 計算オプションフラグ
|
||||
data.calc_income_tax =
|
||||
document.getElementById("calcIncomeTax")?.checked ?? true;
|
||||
data.calc_social_insurance =
|
||||
document.getElementById("calcSocialInsurance")?.checked ?? true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
@@ -942,6 +1069,7 @@
|
||||
const result = await response.json();
|
||||
alert("給与計算が完了しました");
|
||||
closeCalculateForm();
|
||||
await loadPayrolls();
|
||||
viewPayroll(result.payroll_id);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
@@ -1188,6 +1316,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>計算オプション</h3>
|
||||
<div style="display:flex; gap:30px; padding:8px 0;">
|
||||
<label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
|
||||
<input type="checkbox" id="editCalcIncomeTax" checked />
|
||||
所得税を計算する
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
|
||||
<input type="checkbox" id="editCalcSocialInsurance" checked />
|
||||
社会保険を計算する
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">保存して再計算</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="this.closest('div').remove()">キャンセル</button>
|
||||
</form>
|
||||
@@ -1224,11 +1366,20 @@
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
// チェックボックスの値をフォーム削除前に取得
|
||||
const calcIncomeTax =
|
||||
document.getElementById("editCalcIncomeTax")?.checked ?? true;
|
||||
const calcSocialInsurance =
|
||||
document.getElementById("editCalcSocialInsurance")?.checked ??
|
||||
true;
|
||||
alert("給与データを更新しました");
|
||||
form.closest("div").remove();
|
||||
|
||||
// 再計算
|
||||
await recalculatePayroll(payrollId);
|
||||
await recalculatePayroll(payrollId, {
|
||||
calc_income_tax: calcIncomeTax,
|
||||
calc_social_insurance: calcSocialInsurance,
|
||||
});
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("更新に失敗しました: " + error.detail);
|
||||
@@ -1266,14 +1417,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculatePayroll(payrollId) {
|
||||
async function recalculatePayroll(payrollId, calcOptions = null) {
|
||||
if (!confirm("給与を再計算しますか?")) return;
|
||||
|
||||
try {
|
||||
const body = calcOptions || {};
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}/recalculate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1320,11 +1474,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 初期化: 月ピッカーにセットして一覧を読み込む
|
||||
// 初期化: 年フィルタに現在年をセットして一覧を読み込む
|
||||
const now = new Date();
|
||||
const ym =
|
||||
now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
|
||||
document.getElementById("filterYearMonth").value = ym;
|
||||
const filterYearEl = document.getElementById("filterYear");
|
||||
if (filterYearEl) filterYearEl.value = now.getFullYear();
|
||||
document.getElementById("filterBonusYearMonth").value = ym;
|
||||
loadPayrolls();
|
||||
|
||||
@@ -1359,8 +1514,11 @@
|
||||
now.getFullYear() +
|
||||
"-" +
|
||||
String(now.getMonth() + 1).padStart(2, "0");
|
||||
document.getElementById("voucherStartYearMonth").value = ym;
|
||||
document.getElementById("voucherEndYearMonth").value = "";
|
||||
document.getElementById("voucherStartYear").value = now.getFullYear();
|
||||
document.getElementById("voucherStartMonth").value =
|
||||
now.getMonth() + 1;
|
||||
document.getElementById("voucherEndYear").value = "";
|
||||
document.getElementById("voucherEndMonth").value = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1732,8 +1890,13 @@
|
||||
|
||||
async function printVouchers() {
|
||||
// 検証
|
||||
const startYM = document.getElementById("voucherStartYearMonth").value;
|
||||
if (!startYM) {
|
||||
const startYear = Number(
|
||||
document.getElementById("voucherStartYear").value,
|
||||
);
|
||||
const startMonth = Number(
|
||||
document.getElementById("voucherStartMonth").value,
|
||||
);
|
||||
if (!startYear || !startMonth) {
|
||||
alert("開始年月を選択してください");
|
||||
return;
|
||||
}
|
||||
@@ -1745,11 +1908,10 @@
|
||||
}
|
||||
|
||||
// 年月を解析
|
||||
const [startYear, startMonth] = startYM.split("-").map(Number);
|
||||
const endYM = document.getElementById("voucherEndYearMonth").value;
|
||||
const endParts = endYM ? endYM.split("-").map(Number) : null;
|
||||
const endYear = endParts ? endParts[0] : null;
|
||||
const endMonth = endParts ? endParts[1] : null;
|
||||
const endYearVal = document.getElementById("voucherEndYear").value;
|
||||
const endMonthVal = document.getElementById("voucherEndMonth").value;
|
||||
const endYear = endYearVal ? Number(endYearVal) : null;
|
||||
const endMonth = endMonthVal ? Number(endMonthVal) : null;
|
||||
|
||||
const voucherType = document.querySelector(
|
||||
"input[name='voucherType']:checked",
|
||||
@@ -1900,6 +2062,7 @@
|
||||
<div class="page-title">${salary.payroll_year}年${String(salary.payroll_month).padStart(2, "0")}月 給与明細</div>
|
||||
<div class="employee-info">従業員: ${emp.employee_code} ${emp.employee_name}</div>
|
||||
<div class="employee-info" style="color: #666; font-size: 12px;">支給日: ${paymentDate}</div>
|
||||
${emp.employment_type ? `<div class="employee-info" style="color: #666; font-size: 12px;">雇用形態:${emp.employment_type}</div>` : ""}
|
||||
|
||||
<div class="section-title">勤怠情報</div>
|
||||
<table class="detail-table">
|
||||
@@ -2013,6 +2176,7 @@
|
||||
<div class="page-title">${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月 賞与明細${bonus.bonus_type ? ` (${bonus.bonus_type})` : ""}</div>
|
||||
<div class="employee-info">従業員: ${emp.employee_code} ${emp.employee_name}</div>
|
||||
<div class="employee-info" style="color: #666; font-size: 12px;">支給日: ${bonusPaymentDate}</div>
|
||||
${emp.employment_type ? `<div class="employee-info" style="color: #666; font-size: 12px;">雇用形態:${emp.employment_type}</div>` : ""}
|
||||
|
||||
<div class="section-title">支給項目</div>
|
||||
<table class="detail-table">
|
||||
@@ -2103,8 +2267,13 @@
|
||||
|
||||
async function exportVouchersCSV() {
|
||||
// 検証
|
||||
const startYM = document.getElementById("voucherStartYearMonth").value;
|
||||
if (!startYM) {
|
||||
const startYear = Number(
|
||||
document.getElementById("voucherStartYear").value,
|
||||
);
|
||||
const startMonth = Number(
|
||||
document.getElementById("voucherStartMonth").value,
|
||||
);
|
||||
if (!startYear || !startMonth) {
|
||||
alert("開始年月を選択してください");
|
||||
return;
|
||||
}
|
||||
@@ -2115,11 +2284,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const [startYear, startMonth] = startYM.split("-").map(Number);
|
||||
const endYM = document.getElementById("voucherEndYearMonth").value;
|
||||
const endParts = endYM ? endYM.split("-").map(Number) : null;
|
||||
const endYear = endParts ? endParts[0] : null;
|
||||
const endMonth = endParts ? endParts[1] : null;
|
||||
const endYearVal = document.getElementById("voucherEndYear").value;
|
||||
const endMonthVal = document.getElementById("voucherEndMonth").value;
|
||||
const endYear = endYearVal ? Number(endYearVal) : null;
|
||||
const endMonth = endMonthVal ? Number(endMonthVal) : null;
|
||||
const voucherType = document.querySelector(
|
||||
"input[name='voucherType']:checked",
|
||||
).value;
|
||||
|
||||
@@ -326,10 +326,9 @@
|
||||
: "✗ 非対象"
|
||||
}</td>
|
||||
<td>${s.valid_from} ~ ${s.valid_to || "現在"}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" onclick="editSetting(${
|
||||
s.setting_id
|
||||
})" style="padding: 5px 10px; font-size: 12px;">編集</button>
|
||||
<td style="white-space:nowrap;">
|
||||
<button class="btn btn-primary" onclick="editSetting(${s.setting_id})" style="padding: 5px 10px; font-size: 12px;">編集</button>
|
||||
<button class="btn" onclick="deleteSetting(${s.setting_id}, '${s.employee_name}', '${s.valid_from}')" style="padding: 5px 10px; font-size: 12px; background:#dc3545; color:white; margin-left:4px;">削除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
@@ -390,6 +389,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSetting(settingId, employeeName, validFrom) {
|
||||
if (
|
||||
!confirm(
|
||||
`「${employeeName}」の適用開始日 ${validFrom} の給与設定を削除しますか?\nこの操作は元に戻せません。`,
|
||||
)
|
||||
)
|
||||
return;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${settingId}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
);
|
||||
if (response.ok || response.status === 204) {
|
||||
alert("給与設定を削除しました");
|
||||
loadAllSalarySettings();
|
||||
} else {
|
||||
let errMsg = `HTTP ${response.status}`;
|
||||
try {
|
||||
const errBody = await response.json();
|
||||
if (errBody.detail) errMsg = String(errBody.detail);
|
||||
} catch (_) {}
|
||||
alert("削除に失敗しました: " + errMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("削除に失敗しました: " + (error.message || error));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddForm() {
|
||||
document.getElementById("formTitle").textContent = "給与設定の追加";
|
||||
document.getElementById("addForm").style.display = "block";
|
||||
@@ -416,8 +446,18 @@
|
||||
const formData = new FormData(form);
|
||||
const settingId = document.getElementById("setting_id").value;
|
||||
|
||||
// disabled な employee_id は FormData に含まれないため DOM から直接取得
|
||||
const employeeIdRaw =
|
||||
formData.get("employee_id") ||
|
||||
document.getElementById("employee_id").value;
|
||||
const employeeId = parseInt(employeeIdRaw);
|
||||
|
||||
if (!settingId && (!employeeId || isNaN(employeeId))) {
|
||||
alert("従業員を選択してください");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
employee_id: parseInt(formData.get("employee_id")),
|
||||
base_salary: parseFloat(formData.get("base_salary")) || 0,
|
||||
employment_type: formData.get("employment_type"),
|
||||
payment_type: formData.get("payment_type"),
|
||||
@@ -429,6 +469,11 @@
|
||||
valid_from: formData.get("valid_from"),
|
||||
};
|
||||
|
||||
// 新規登録のみ employee_id を含める
|
||||
if (!settingId) {
|
||||
data.employee_id = employeeId;
|
||||
}
|
||||
|
||||
if (formData.get("hourly_rate")) {
|
||||
data.hourly_rate = parseFloat(formData.get("hourly_rate"));
|
||||
}
|
||||
@@ -465,11 +510,24 @@
|
||||
hideAddForm();
|
||||
loadAllSalarySettings();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("保存に失敗しました: " + error.detail);
|
||||
let errMsg = `HTTP ${response.status}`;
|
||||
try {
|
||||
const errBody = await response.json();
|
||||
const detail = errBody.detail;
|
||||
if (Array.isArray(detail)) {
|
||||
errMsg = detail
|
||||
.map((e) => e.msg || JSON.stringify(e))
|
||||
.join(", ");
|
||||
} else if (detail) {
|
||||
errMsg = String(detail);
|
||||
}
|
||||
} catch (_) {
|
||||
errMsg += ` ${await response.text().catch(() => "")}`;
|
||||
}
|
||||
alert("保存に失敗しました: " + errMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("保存に失敗しました");
|
||||
alert("保存に失敗しました: " + (error.message || error));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user