更新
This commit is contained in:
@@ -16,8 +16,8 @@ app = FastAPI(
|
|||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"], # 本地开发先用 *
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=False,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
credit = D(row["credit"])
|
credit = D(row["credit"])
|
||||||
|
|
||||||
closing = opening + debit - credit
|
closing = opening + debit - credit
|
||||||
|
|
||||||
|
# 負債・資本は絶対値で表示(見やすくするため)
|
||||||
|
if acc["account_type"] in ("liability", "equity"):
|
||||||
|
opening = abs(opening)
|
||||||
|
closing = abs(closing)
|
||||||
|
|
||||||
account_data[code] = {
|
account_data[code] = {
|
||||||
"account_id": aid,
|
"account_id": aid,
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ class JournalEntryDeleteRequest(BaseModel):
|
|||||||
def get_journal_entries(
|
def get_journal_entries(
|
||||||
from_date: date = None,
|
from_date: date = None,
|
||||||
to_date: date = None,
|
to_date: date = None,
|
||||||
keyword: str = None
|
keyword: str = None,
|
||||||
|
account_id: int = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
仕訳一覧を検索して取得
|
仕訳一覧を検索して取得
|
||||||
@@ -73,6 +74,14 @@ def get_journal_entries(
|
|||||||
sql += " AND je.description LIKE %s"
|
sql += " AND je.description LIKE %s"
|
||||||
params.append(f"%{keyword}%")
|
params.append(f"%{keyword}%")
|
||||||
|
|
||||||
|
if account_id:
|
||||||
|
sql += """ AND je.journal_entry_id IN (
|
||||||
|
SELECT DISTINCT journal_entry_id
|
||||||
|
FROM journal_lines
|
||||||
|
WHERE account_id = %s
|
||||||
|
)"""
|
||||||
|
params.append(account_id)
|
||||||
|
|
||||||
sql += """
|
sql += """
|
||||||
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year
|
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year
|
||||||
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
|
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
|
||||||
@@ -116,7 +125,9 @@ def get_journal_entry(journal_id: int):
|
|||||||
a.account_code,
|
a.account_code,
|
||||||
a.account_name,
|
a.account_name,
|
||||||
jl.debit,
|
jl.debit,
|
||||||
jl.credit
|
jl.credit,
|
||||||
|
jl.tax_rate,
|
||||||
|
jl.tax_direction
|
||||||
FROM journal_lines jl
|
FROM journal_lines jl
|
||||||
JOIN accounts a ON jl.account_id = a.account_id
|
JOIN accounts a ON jl.account_id = a.account_id
|
||||||
WHERE jl.journal_entry_id = %s
|
WHERE jl.journal_entry_id = %s
|
||||||
@@ -301,19 +312,35 @@ def create_journal_entry(req: JournalEntryRequest):
|
|||||||
|
|
||||||
# 明細(税行を含む)
|
# 明細(税行を含む)
|
||||||
for line in all_lines:
|
for line in all_lines:
|
||||||
|
# tax_directionの値を変換: paid->INPUT, received->OUTPUT, none->NONE
|
||||||
|
tax_dir_db = None
|
||||||
|
if line.tax_direction:
|
||||||
|
if line.tax_direction == 'paid':
|
||||||
|
tax_dir_db = 'INPUT'
|
||||||
|
elif line.tax_direction == 'received':
|
||||||
|
tax_dir_db = 'OUTPUT'
|
||||||
|
elif line.tax_direction == 'none':
|
||||||
|
tax_dir_db = 'NONE'
|
||||||
|
else:
|
||||||
|
tax_dir_db = line.tax_direction.upper()
|
||||||
|
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
INSERT INTO journal_lines (
|
INSERT INTO journal_lines (
|
||||||
journal_entry_id,
|
journal_entry_id,
|
||||||
account_id,
|
account_id,
|
||||||
debit,
|
debit,
|
||||||
credit
|
credit,
|
||||||
|
tax_rate,
|
||||||
|
tax_direction
|
||||||
)
|
)
|
||||||
VALUES (%s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
""", (
|
""", (
|
||||||
entry_id,
|
entry_id,
|
||||||
line.account_id,
|
line.account_id,
|
||||||
line.debit,
|
line.debit,
|
||||||
line.credit
|
line.credit,
|
||||||
|
line.tax_rate,
|
||||||
|
tax_dir_db
|
||||||
))
|
))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -177,7 +177,12 @@
|
|||||||
<input type="date" id="searchToDate" min="1900-01-01" max="2999-12-31" />
|
<input type="date" id="searchToDate" min="1900-01-01" max="2999-12-31" />
|
||||||
|
|
||||||
<label style="margin-left: 16px">摘要</label>
|
<label style="margin-left: 16px">摘要</label>
|
||||||
<input type="text" id="searchKeyword" />
|
<input type="text" id="searchKeyword" style="min-width: 600px" />
|
||||||
|
|
||||||
|
<label style="margin-left: 16px">科目</label>
|
||||||
|
<select id="searchAccountId" style="min-width: 200px">
|
||||||
|
<option value="">-- すべて --</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,6 +205,17 @@
|
|||||||
|
|
||||||
const API = "http://127.0.0.1:18080";
|
const API = "http://127.0.0.1:18080";
|
||||||
|
|
||||||
|
// 千分位カンマフォーマット関数
|
||||||
|
function formatNumberInput(input) {
|
||||||
|
let value = input.value.replace(/,/g, ""); // 既存のカンマを削除
|
||||||
|
if (value === "" || isNaN(value)) {
|
||||||
|
input.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 数値をカンマ区切りに変換
|
||||||
|
input.value = Number(value).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
let accounts = [];
|
let accounts = [];
|
||||||
|
|
||||||
const ACCOUNT_GROUPS = [
|
const ACCOUNT_GROUPS = [
|
||||||
@@ -269,6 +285,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
setupTaxSelectors();
|
setupTaxSelectors();
|
||||||
|
setupSearchAccountSelector();
|
||||||
addRow();
|
addRow();
|
||||||
|
|
||||||
document.getElementById("entryDate").value = new Date()
|
document.getElementById("entryDate").value = new Date()
|
||||||
@@ -283,6 +300,32 @@
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// 検索用科目セレクタ
|
||||||
|
// -----------------------------
|
||||||
|
function setupSearchAccountSelector() {
|
||||||
|
const select = document.getElementById("searchAccountId");
|
||||||
|
select.innerHTML = '<option value="">-- すべて --</option>';
|
||||||
|
|
||||||
|
ACCOUNT_GROUPS.forEach((group) => {
|
||||||
|
const optgroup = document.createElement("optgroup");
|
||||||
|
optgroup.label = group.label;
|
||||||
|
|
||||||
|
const groupAccounts = accounts.filter((a) =>
|
||||||
|
group.codes.includes(a.account_code)
|
||||||
|
);
|
||||||
|
|
||||||
|
groupAccounts.forEach((acc) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = acc.account_id;
|
||||||
|
option.textContent = `${acc.account_code} ${acc.account_name}`;
|
||||||
|
optgroup.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
select.appendChild(optgroup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// 摘要履歴管理
|
// 摘要履歴管理
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -358,35 +401,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>
|
<td>
|
||||||
<select class="account" ${isTaxRow ? "disabled" : ""}>
|
<select class="account" ${isTaxRow ? "disabled" : ""}>
|
||||||
${buildAccountOptions()}
|
${buildAccountOptions()}
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td><input type="number" class="debit right" min="0" ${
|
|
||||||
isTaxRow ? "readonly" : ""
|
|
||||||
}></td>
|
|
||||||
<td><input type="number" class="credit right" min="0" ${
|
|
||||||
isTaxRow ? "readonly" : ""
|
|
||||||
}></td>
|
|
||||||
<td>
|
|
||||||
<select class="taxType" ${isTaxRow ? "disabled" : ""}>
|
|
||||||
<option value="none">対象外</option>
|
|
||||||
<option value="10">10%</option>
|
|
||||||
<option value="8">8%</option>
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td><input type="text" class="debit right" ${
|
||||||
<select class="taxDirection" ${isTaxRow ? "disabled" : ""}>
|
isTaxRow ? "readonly" : ""
|
||||||
<option value="none">対象外</option>
|
} oninput="formatNumberInput(this)"></td>
|
||||||
<option value="paid">仮払(仕入・経費)</option>
|
<td><input type="text" class="credit right" ${
|
||||||
<option value="received">仮受(売上)</option>
|
isTaxRow ? "readonly" : ""
|
||||||
</select>
|
} oninput="formatNumberInput(this)"></td>
|
||||||
</td>
|
<td>
|
||||||
<td>${
|
<select class="taxType" ${isTaxRow ? "disabled" : ""}>
|
||||||
isTaxRow ? "" : `<button onclick="removeRow(this)">削除</button>`
|
<option value="none">対象外</option>
|
||||||
}</td>
|
<option value="10">10%</option>
|
||||||
`;
|
<option value="8">8%</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select class="taxDirection" ${isTaxRow ? "disabled" : ""}>
|
||||||
|
<option value="none">対象外</option>
|
||||||
|
<option value="paid">仮払(仕入・経費)</option>
|
||||||
|
<option value="received">仮受(売上)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>${
|
||||||
|
isTaxRow ? "" : `<button onclick="removeRow(this)">削除</button>`
|
||||||
|
}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
@@ -436,8 +479,12 @@
|
|||||||
|
|
||||||
const taxType = row.querySelector(".taxType").value;
|
const taxType = row.querySelector(".taxType").value;
|
||||||
const taxDir = row.querySelector(".taxDirection").value;
|
const taxDir = row.querySelector(".taxDirection").value;
|
||||||
const debit = Number(row.querySelector(".debit").value || 0);
|
const debit = Number(
|
||||||
const credit = Number(row.querySelector(".credit").value || 0);
|
row.querySelector(".debit").value.replace(/,/g, "") || 0
|
||||||
|
);
|
||||||
|
const credit = Number(
|
||||||
|
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
||||||
|
);
|
||||||
|
|
||||||
if (taxType === "none" || taxDir === "none") return;
|
if (taxType === "none" || taxDir === "none") return;
|
||||||
|
|
||||||
@@ -503,86 +550,145 @@
|
|||||||
// 登録(税行自動生成版)
|
// 登録(税行自動生成版)
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
async function submitJournal() {
|
async function submitJournal() {
|
||||||
const entryDate = document.getElementById("entryDate").value;
|
try {
|
||||||
const description = document.getElementById("description").value.trim();
|
const entryDate = document.getElementById("entryDate").value;
|
||||||
|
const description = document
|
||||||
|
.getElementById("description")
|
||||||
|
.value.trim();
|
||||||
|
|
||||||
if (!entryDate) {
|
if (!entryDate) {
|
||||||
alert("仕訳日を入力してください");
|
alert("仕訳日を入力してください");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// 明細収集(税情報も含む)
|
|
||||||
const rows = document.querySelectorAll("#linesTable tbody tr");
|
|
||||||
const lines = [];
|
|
||||||
|
|
||||||
rows.forEach((row) => {
|
|
||||||
if (row.classList.contains("tax-row")) return; // 税行は無視
|
|
||||||
|
|
||||||
const accountId = Number(row.querySelector(".account").value);
|
|
||||||
const debit = Number(row.querySelector(".debit").value || 0);
|
|
||||||
const credit = Number(row.querySelector(".credit").value || 0);
|
|
||||||
const taxType = row.querySelector(".taxType").value;
|
|
||||||
const taxDirection = row.querySelector(".taxDirection").value;
|
|
||||||
|
|
||||||
if (debit > 0 && credit > 0) {
|
|
||||||
alert("同一行不能同时填写借方和贷方");
|
|
||||||
throw new Error("invalid line");
|
|
||||||
}
|
}
|
||||||
if (debit === 0 && credit === 0) return;
|
|
||||||
|
|
||||||
const lineData = {
|
// 明細収集(税情報も含む)
|
||||||
account_id: accountId,
|
const rows = document.querySelectorAll("#linesTable tbody tr");
|
||||||
debit: debit,
|
const lines = [];
|
||||||
credit: credit,
|
|
||||||
|
rows.forEach((row) => {
|
||||||
|
if (row.classList.contains("tax-row")) return; // 税行は無視
|
||||||
|
|
||||||
|
const accountId = Number(row.querySelector(".account").value);
|
||||||
|
const debit = Number(
|
||||||
|
row.querySelector(".debit").value.replace(/,/g, "") || 0
|
||||||
|
);
|
||||||
|
const credit = Number(
|
||||||
|
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
||||||
|
);
|
||||||
|
const taxType = row.querySelector(".taxType").value;
|
||||||
|
const taxDirection = row.querySelector(".taxDirection").value;
|
||||||
|
|
||||||
|
if (debit > 0 && credit > 0) {
|
||||||
|
alert("同一行不能同时填写借方和贷方");
|
||||||
|
throw new Error("invalid line");
|
||||||
|
}
|
||||||
|
if (debit === 0 && credit === 0) return;
|
||||||
|
|
||||||
|
const lineData = {
|
||||||
|
account_id: accountId,
|
||||||
|
debit: debit,
|
||||||
|
credit: credit,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 税情報を追加
|
||||||
|
if (taxType !== "none" && taxDirection !== "none") {
|
||||||
|
lineData.tax_rate = parseInt(taxType);
|
||||||
|
lineData.tax_direction = taxDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(lineData);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lines.length === 0) {
|
||||||
|
alert("仕訳明細がありません");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 税科目(仮払消費税等・仮受消費税等)が存在するかチェック
|
||||||
|
const hasTaxAccount = lines.some((line) => {
|
||||||
|
const account = accounts.find(
|
||||||
|
(a) => a.account_id === line.account_id
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
account &&
|
||||||
|
(account.account_name.includes("仮払消費税") ||
|
||||||
|
account.account_name.includes("仮受消費税"))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 税科目が存在し、かつ税情報を持つ行がある場合は警告
|
||||||
|
const hasTaxInfo = lines.some(
|
||||||
|
(line) => line.tax_rate != null && line.tax_direction != null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasTaxAccount && hasTaxInfo) {
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
"警告:仮払消費税等・仮受消費税等の行がすでに存在していますが、他の行に税区分・税方向が設定されています。\n" +
|
||||||
|
"このまま登録すると税が二重計算され、借貸不平衡になります。\n\n" +
|
||||||
|
"税区分・税方向をすべて「対象外」に設定してから登録することをお勧めします。\n\n" +
|
||||||
|
"このまま登録しますか?"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
entry_date: entryDate,
|
||||||
|
description,
|
||||||
|
lines,
|
||||||
|
tax_paid_account_id:
|
||||||
|
Number(document.getElementById("taxPaidSelect").value) || null,
|
||||||
|
tax_received_account_id:
|
||||||
|
Number(document.getElementById("taxReceivedSelect").value) ||
|
||||||
|
null,
|
||||||
|
rounding_mode: document.getElementById("roundingMode").value,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 税情報を追加
|
console.log("送信するデータ:", JSON.stringify(payload, null, 2));
|
||||||
if (taxType !== "none" && taxDirection !== "none") {
|
console.log("明細行数:", lines.length);
|
||||||
lineData.tax_rate = parseInt(taxType);
|
lines.forEach((line, index) => {
|
||||||
lineData.tax_direction = taxDirection;
|
console.log(`行${index + 1}:`, line);
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch(`${API}/journal-entries`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const errorData = await res.json().catch(() => ({}));
|
||||||
|
const errorMsg =
|
||||||
|
errorData.detail || `登録に失敗しました (HTTP ${res.status})`;
|
||||||
|
console.error("Error:", errorData);
|
||||||
|
alert(errorMsg);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push(lineData);
|
const result = await res.json();
|
||||||
});
|
|
||||||
|
|
||||||
if (lines.length === 0) {
|
// 摘要を履歴に保存
|
||||||
alert("仕訳明細がありません");
|
saveDescriptionToHistory(description);
|
||||||
return;
|
|
||||||
|
alert(`登録完了(ID=${result.journal_entry_id})`);
|
||||||
|
|
||||||
|
// フォームをクリア
|
||||||
|
document.getElementById("description").value = "";
|
||||||
|
const tbody = document.querySelector("#linesTable tbody");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
rowSeq = 0;
|
||||||
|
addRow();
|
||||||
|
|
||||||
|
// 仕訳検索を自動実行して最新の登録状況を表示
|
||||||
|
searchJournals();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("送信エラー:", error);
|
||||||
|
alert(
|
||||||
|
`エラーが発生しました: ${error.message}\n\nサーバーが起動しているか確認してください。`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
|
||||||
entry_date: entryDate,
|
|
||||||
description,
|
|
||||||
lines,
|
|
||||||
tax_paid_account_id:
|
|
||||||
Number(document.getElementById("taxPaidSelect").value) || null,
|
|
||||||
tax_received_account_id:
|
|
||||||
Number(document.getElementById("taxReceivedSelect").value) || null,
|
|
||||||
rounding_mode: document.getElementById("roundingMode").value,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(`${API}/journal-entries`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const errorData = await res.json().catch(() => ({}));
|
|
||||||
const errorMsg =
|
|
||||||
errorData.detail || `登録に失敗しました (HTTP ${res.status})`;
|
|
||||||
console.error("Error:", errorData);
|
|
||||||
alert(errorMsg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await res.json();
|
|
||||||
|
|
||||||
// 摘要を履歴に保存
|
|
||||||
saveDescriptionToHistory(description);
|
|
||||||
|
|
||||||
alert(`登録完了(ID=${result.journal_entry_id})`);
|
|
||||||
location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE = "http://127.0.0.1:18080";
|
const API_BASE = "http://127.0.0.1:18080";
|
||||||
@@ -731,8 +837,8 @@
|
|||||||
.filter((a) => group.codes.includes(a.account_code))
|
.filter((a) => group.codes.includes(a.account_code))
|
||||||
.forEach((a) => {
|
.forEach((a) => {
|
||||||
html += `<option value="${a.account_id}">
|
html += `<option value="${a.account_id}">
|
||||||
${a.account_code} ${a.account_name}
|
${a.account_code} ${a.account_name}
|
||||||
</option>`;
|
</option>`;
|
||||||
});
|
});
|
||||||
html += `</optgroup>`;
|
html += `</optgroup>`;
|
||||||
});
|
});
|
||||||
@@ -747,8 +853,8 @@
|
|||||||
html += `<optgroup label="📚 その他">`;
|
html += `<optgroup label="📚 その他">`;
|
||||||
others.forEach((a) => {
|
others.forEach((a) => {
|
||||||
html += `<option value="${a.account_id}">
|
html += `<option value="${a.account_id}">
|
||||||
${a.account_code} ${a.account_name}
|
${a.account_code} ${a.account_name}
|
||||||
</option>`;
|
</option>`;
|
||||||
});
|
});
|
||||||
html += `</optgroup>`;
|
html += `</optgroup>`;
|
||||||
}
|
}
|
||||||
@@ -763,6 +869,7 @@
|
|||||||
const from = document.getElementById("searchFromDate").value;
|
const from = document.getElementById("searchFromDate").value;
|
||||||
const to = document.getElementById("searchToDate").value;
|
const to = document.getElementById("searchToDate").value;
|
||||||
const key = document.getElementById("searchKeyword").value;
|
const key = document.getElementById("searchKeyword").value;
|
||||||
|
const accountId = document.getElementById("searchAccountId").value;
|
||||||
|
|
||||||
// 検索条件を保存
|
// 検索条件を保存
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@@ -771,6 +878,7 @@
|
|||||||
fromDate: from,
|
fromDate: from,
|
||||||
toDate: to,
|
toDate: to,
|
||||||
keyword: key,
|
keyword: key,
|
||||||
|
accountId: accountId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -778,6 +886,7 @@
|
|||||||
if (from) qs.append("from_date", from);
|
if (from) qs.append("from_date", from);
|
||||||
if (to) qs.append("to_date", to);
|
if (to) qs.append("to_date", to);
|
||||||
if (key) qs.append("keyword", key);
|
if (key) qs.append("keyword", key);
|
||||||
|
if (accountId) qs.append("account_id", accountId);
|
||||||
|
|
||||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -788,21 +897,25 @@
|
|||||||
data.forEach((e) => {
|
data.forEach((e) => {
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${e.entry_date}</td>
|
<td>${e.entry_date}</td>
|
||||||
<td>${e.description}</td>
|
<td>${e.description}</td>
|
||||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||||
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||||
<td>
|
<td>
|
||||||
<button onclick="viewJournal(${e.journal_entry_id})">表示</button>
|
<button onclick="viewJournal(${
|
||||||
<button onclick="copyJournal(${
|
e.journal_entry_id
|
||||||
e.journal_entry_id
|
})">表示</button>
|
||||||
})" style="color: green;">複製</button>
|
<button onclick="copyJournal(${
|
||||||
<button onclick="reverseAndEdit(${e.journal_entry_id})">修正</button>
|
e.journal_entry_id
|
||||||
<button onclick="deleteJournalEntry(${
|
})" style="color: green;">複製</button>
|
||||||
e.journal_entry_id
|
<button onclick="reverseAndEdit(${
|
||||||
})" style="color: red;">削除</button>
|
e.journal_entry_id
|
||||||
</td>
|
})">修正</button>
|
||||||
`;
|
<button onclick="deleteJournalEntry(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})" style="color: red;">削除</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -824,10 +937,8 @@
|
|||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
// 日付は今日の日付をデフォルトに(必要に応じて変更可能)
|
// 日付をコピー元の仕訳日に設定
|
||||||
document.getElementById("entryDate").value = new Date()
|
document.getElementById("entryDate").value = data.entry_date;
|
||||||
.toISOString()
|
|
||||||
.slice(0, 10);
|
|
||||||
|
|
||||||
// 摘要をコピー
|
// 摘要をコピー
|
||||||
document.getElementById("description").value = data.description || "";
|
document.getElementById("description").value = data.description || "";
|
||||||
@@ -837,7 +948,8 @@
|
|||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
rowSeq = 0;
|
rowSeq = 0;
|
||||||
|
|
||||||
// 仕訳明細をコピー
|
// 仕訳明細をすべてコピー(税行も含む)
|
||||||
|
// 税の自動計算機能は実行しない(税区分・税方向を「対象外」に設定)
|
||||||
if (data.lines && data.lines.length > 0) {
|
if (data.lines && data.lines.length > 0) {
|
||||||
data.lines.forEach((line) => {
|
data.lines.forEach((line) => {
|
||||||
const tr = addRow();
|
const tr = addRow();
|
||||||
@@ -848,26 +960,25 @@
|
|||||||
accountSelect.value = line.account_id;
|
accountSelect.value = line.account_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 借方・貸方金額を設定
|
// 借方・貸方金額を設定(カンマフォーマット付き)
|
||||||
const debitInput = tr.querySelector(".debit");
|
const debitInput = tr.querySelector(".debit");
|
||||||
const creditInput = tr.querySelector(".credit");
|
const creditInput = tr.querySelector(".credit");
|
||||||
if (debitInput && line.debit) {
|
if (debitInput && line.debit) {
|
||||||
debitInput.value = line.debit;
|
debitInput.value = Number(line.debit).toLocaleString();
|
||||||
}
|
}
|
||||||
if (creditInput && line.credit) {
|
if (creditInput && line.credit) {
|
||||||
creditInput.value = line.credit;
|
creditInput.value = Number(line.credit).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 税区分を設定
|
// 税区分・税方向は「対象外」に設定(自動計算を防ぐため)
|
||||||
const taxTypeSelect = tr.querySelector(".taxType");
|
const taxTypeSelect = tr.querySelector(".taxType");
|
||||||
const taxDirSelect = tr.querySelector(".taxDirection");
|
const taxDirSelect = tr.querySelector(".taxDirection");
|
||||||
|
|
||||||
if (line.tax_rate && taxTypeSelect) {
|
if (taxTypeSelect) {
|
||||||
taxTypeSelect.value = String(line.tax_rate);
|
taxTypeSelect.value = "none"; // 対象外
|
||||||
}
|
}
|
||||||
|
if (taxDirSelect) {
|
||||||
if (line.tax_direction && taxDirSelect) {
|
taxDirSelect.value = "none"; // 対象外
|
||||||
taxDirSelect.value = line.tax_direction;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -875,8 +986,12 @@
|
|||||||
// ページ上部の仕訳入力フォームにスクロール
|
// ページ上部の仕訳入力フォームにスクロール
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
|
||||||
|
console.log("コピーされたデータ:", data); // デバッグ用
|
||||||
|
console.log(
|
||||||
|
`${data.lines.length}行をコピーしました。税区分・税方向はすべて「対象外」に設定されています。`
|
||||||
|
);
|
||||||
alert(
|
alert(
|
||||||
"仕訳を複製しました。日付や金額などを修正して登録してください。"
|
`仕訳を複製しました(${data.lines.length}行)。\n税の自動計算を防ぐため、すべての行の税区分・税方向を「対象外」に設定しています。\n必要に応じて修正してから登録してください。`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
@@ -962,7 +1077,7 @@
|
|||||||
searchJournals(); // リストを再読み込み
|
searchJournals(); // リストを再読み込み
|
||||||
}
|
}
|
||||||
|
|
||||||
// ページロード後に前回の検索条件を復元
|
// ページロード後に検索条件を設定
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
// デフォルト期間を設定(5/31決算:6月1日~現在)
|
// デフォルト期間を設定(5/31決算:6月1日~現在)
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
@@ -981,29 +1096,23 @@
|
|||||||
const defaultFromDate = `${fiscalStartYear}-06-01`;
|
const defaultFromDate = `${fiscalStartYear}-06-01`;
|
||||||
const defaultToDate = today.toISOString().slice(0, 10);
|
const defaultToDate = today.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
// 常にデフォルト期間を設定(会計年度に合わせるため)
|
||||||
|
document.getElementById("searchFromDate").value = defaultFromDate;
|
||||||
|
document.getElementById("searchToDate").value = defaultToDate;
|
||||||
|
|
||||||
|
// 摘要と科目のみ前回の検索条件を復元
|
||||||
const savedConditions = localStorage.getItem("journalSearchConditions");
|
const savedConditions = localStorage.getItem("journalSearchConditions");
|
||||||
if (savedConditions) {
|
if (savedConditions) {
|
||||||
const conditions = JSON.parse(savedConditions);
|
const conditions = JSON.parse(savedConditions);
|
||||||
if (conditions.fromDate)
|
|
||||||
document.getElementById("searchFromDate").value =
|
|
||||||
conditions.fromDate;
|
|
||||||
else
|
|
||||||
document.getElementById("searchFromDate").value = defaultFromDate;
|
|
||||||
|
|
||||||
if (conditions.toDate)
|
|
||||||
document.getElementById("searchToDate").value = conditions.toDate;
|
|
||||||
else document.getElementById("searchToDate").value = defaultToDate;
|
|
||||||
|
|
||||||
if (conditions.keyword)
|
if (conditions.keyword)
|
||||||
document.getElementById("searchKeyword").value = conditions.keyword;
|
document.getElementById("searchKeyword").value = conditions.keyword;
|
||||||
|
if (conditions.accountId)
|
||||||
// 自動的に検索を実行
|
document.getElementById("searchAccountId").value =
|
||||||
searchJournals();
|
conditions.accountId;
|
||||||
} else {
|
|
||||||
// 保存された条件がない場合はデフォルト値を設定
|
|
||||||
document.getElementById("searchFromDate").value = defaultFromDate;
|
|
||||||
document.getElementById("searchToDate").value = defaultToDate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ページロード時に自動的に検索を実行して最新データを表示
|
||||||
|
searchJournals();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
window.location.href = "journal-list.html";
|
window.location.href = "journal-entry.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
loadJournalEntry();
|
loadJournalEntry();
|
||||||
|
|||||||
@@ -6,12 +6,14 @@
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: 'MS Gothic', 'Meiryo', sans-serif;
|
font-family: 'MS Gothic', 'Meiryo', sans-serif;
|
||||||
margin: 24px;
|
margin: 24px auto;
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
|
max-width: 1400px;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
.search-form {
|
.search-form {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -24,13 +26,14 @@
|
|||||||
.search-form label {
|
.search-form label {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.search-form input {
|
.search-form input {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.search-form button {
|
.search-form button {
|
||||||
padding: 6px 20px;
|
padding: 6px 20px;
|
||||||
@@ -40,7 +43,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
.search-form button:hover {
|
.search-form button:hover {
|
||||||
background: #45a049;
|
background: #45a049;
|
||||||
@@ -54,23 +57,24 @@
|
|||||||
.period-info {
|
.period-info {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
padding: 5px 8px;
|
padding: 8px 10px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
background: #e8e8e8;
|
background: #e8e8e8;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
td.left {
|
td.left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|||||||
Reference in New Issue
Block a user