login画面追加
This commit is contained in:
@@ -6,7 +6,6 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
|
||||||
# FastAPI アプリケーション作成
|
# FastAPI アプリケーション作成
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="日本小規模企業向け会計システム"
|
title="日本小規模企業向け会計システム"
|
||||||
@@ -32,13 +31,15 @@ async def validation_exception_handler(request: Request, exc: ValidationError):
|
|||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
# ルート(稼働確認)
|
# ルート(稼働確認)
|
||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
#@app.get("/", include_in_schema=False)
|
@app.get("/", include_in_schema=False)
|
||||||
#def root():
|
def root():
|
||||||
# return RedirectResponse(url="/index.html")
|
return RedirectResponse("/login.html")
|
||||||
|
|
||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
# ルーター登録(※ 必ず app 定義の後)
|
# ルーター登録(※ 必ず app 定義の後)
|
||||||
# ─────────────────────────
|
# ─────────────────────────
|
||||||
|
from app.modules.users.router import router as users_router
|
||||||
|
app.include_router(users_router)
|
||||||
|
|
||||||
from app.modules.accounts.router import router as accounts_router
|
from app.modules.accounts.router import router as accounts_router
|
||||||
app.include_router(accounts_router)
|
app.include_router(accounts_router)
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ print("Frontend path =", frontend_path)
|
|||||||
if frontend_path.exists():
|
if frontend_path.exists():
|
||||||
app.mount(
|
app.mount(
|
||||||
"/",
|
"/",
|
||||||
StaticFiles(directory=frontend_path, html=True),
|
StaticFiles(directory=frontend_path),
|
||||||
name="frontend",
|
name="frontend",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
1
backend/app/modules/users/__init__.py
Normal file
1
backend/app/modules/users/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Users Module
|
||||||
104
backend/app/modules/users/router.py
Normal file
104
backend/app/modules/users/router.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/users", tags=["users"])
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequest(BaseModel):
|
||||||
|
username: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class LoginResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
full_name: str | None
|
||||||
|
email: str | None
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password: str) -> str:
|
||||||
|
"""パスワードをハッシュ化"""
|
||||||
|
# 実運用ではより安全なハッシュアルゴリズムを使用してください
|
||||||
|
return hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login", response_model=LoginResponse)
|
||||||
|
async def login(request: LoginRequest):
|
||||||
|
"""ユーザーのログイン処理"""
|
||||||
|
try:
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# ユーザーが存在するか確認
|
||||||
|
cur.execute(
|
||||||
|
"SELECT id, username, password, full_name, email FROM users WHERE username = %s AND is_active = TRUE",
|
||||||
|
(request.username,)
|
||||||
|
)
|
||||||
|
user = cur.fetchone()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=401, detail="ユーザー名またはパスワードが正しくありません")
|
||||||
|
|
||||||
|
# パスワードの検証
|
||||||
|
hashed_password = hash_password(request.password)
|
||||||
|
if user["password"] != hashed_password:
|
||||||
|
raise HTTPException(status_code=401, detail="ユーザー名またはパスワードが正しくありません")
|
||||||
|
|
||||||
|
return LoginResponse(
|
||||||
|
id=user["id"],
|
||||||
|
username=user["username"],
|
||||||
|
full_name=user["full_name"],
|
||||||
|
email=user["email"],
|
||||||
|
message="ログインに成功しました"
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"ログイン処理エラー: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/register")
|
||||||
|
async def register(request: LoginRequest):
|
||||||
|
"""新規ユーザー登録"""
|
||||||
|
try:
|
||||||
|
if len(request.password) < 6:
|
||||||
|
raise HTTPException(status_code=400, detail="パスワードは6文字以上である必要があります")
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# ユーザーが既に存在するか確認
|
||||||
|
cur.execute("SELECT id FROM users WHERE username = %s", (request.username,))
|
||||||
|
if cur.fetchone():
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
raise HTTPException(status_code=400, detail="このユーザー名は既に使用されています")
|
||||||
|
|
||||||
|
# ユーザーを登録
|
||||||
|
hashed_password = hash_password(request.password)
|
||||||
|
cur.execute(
|
||||||
|
"INSERT INTO users (username, password) VALUES (%s, %s) RETURNING id, username",
|
||||||
|
(request.username, hashed_password)
|
||||||
|
)
|
||||||
|
new_user = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": new_user["id"],
|
||||||
|
"username": new_user["username"],
|
||||||
|
"message": "ユーザー登録が完了しました"
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"登録処理エラー: {str(e)}")
|
||||||
14
backend/sql/create_users_table.sql
Normal file
14
backend/sql/create_users_table.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- ユーザーテーブル作成
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(100),
|
||||||
|
full_name VARCHAR(100),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ユーザーの一覧表示用インデックス
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>科目マスタ</title>
|
<title>科目マスタ</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>NJTS 会計システム - メイン</title>
|
<title>NJTS 会計システム - メイン</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, -apple-system, "Yu Gothic UI",
|
font-family:
|
||||||
"Hiragino Kaku Gothic ProN", "メイリオ", sans-serif;
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
"Yu Gothic UI",
|
||||||
|
"Hiragino Kaku Gothic ProN",
|
||||||
|
"メイリオ",
|
||||||
|
sans-serif;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
.grid {
|
.grid {
|
||||||
@@ -46,11 +52,34 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.logout-btn {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.logout-btn:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>NJTS</h1>
|
<h1>NJTS</h1>
|
||||||
|
<div class="user-info">
|
||||||
|
<span>ユーザー: <strong id="username">読み込み中...</strong></span>
|
||||||
|
<button class="logout-btn" onclick="logout()">ログアウト</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="grid">
|
<main class="grid">
|
||||||
@@ -78,6 +107,14 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// ユーザー情報を表示
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (user) {
|
||||||
|
document.getElementById("username").textContent = user.username;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// dropdown toggle
|
// dropdown toggle
|
||||||
const toggle = document.getElementById("accountingToggle");
|
const toggle = document.getElementById("accountingToggle");
|
||||||
const menu = document.getElementById("accountingMenu");
|
const menu = document.getElementById("accountingMenu");
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>修正仕訳入力</title>
|
<title>修正仕訳入力</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -102,9 +103,8 @@
|
|||||||
|
|
||||||
// ヘッダ反映
|
// ヘッダ反映
|
||||||
document.getElementById("entryDate").value = srcData.entry_date;
|
document.getElementById("entryDate").value = srcData.entry_date;
|
||||||
document.getElementById(
|
document.getElementById("description").value =
|
||||||
"description"
|
`【修正後】${srcData.description}`;
|
||||||
).value = `【修正後】${srcData.description}`;
|
|
||||||
|
|
||||||
// 明細反映
|
// 明細反映
|
||||||
srcData.lines.forEach((l) => addRow(l));
|
srcData.lines.forEach((l) => addRow(l));
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
${accounts
|
${accounts
|
||||||
.map(
|
.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
|
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`,
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>仕訳入力</title>
|
<title>仕訳入力</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -393,12 +394,12 @@
|
|||||||
|
|
||||||
// 仮払消費税等(あなたの DB では 505 は「未払消費税等」)
|
// 仮払消費税等(あなたの DB では 505 は「未払消費税等」)
|
||||||
taxPaidAccounts = accounts.filter((a) =>
|
taxPaidAccounts = accounts.filter((a) =>
|
||||||
a.account_name.includes("仮払")
|
a.account_name.includes("仮払"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 仮受消費税等
|
// 仮受消費税等
|
||||||
taxReceivedAccounts = accounts.filter((a) =>
|
taxReceivedAccounts = accounts.filter((a) =>
|
||||||
a.account_name.includes("仮受")
|
a.account_name.includes("仮受"),
|
||||||
);
|
);
|
||||||
|
|
||||||
setupTaxSelectors();
|
setupTaxSelectors();
|
||||||
@@ -432,7 +433,7 @@
|
|||||||
.addEventListener("change", () => {
|
.addEventListener("change", () => {
|
||||||
// 端数処理変更時は全ての税行を再計算
|
// 端数処理変更時は全ての税行を再計算
|
||||||
const rows = document.querySelectorAll(
|
const rows = document.querySelectorAll(
|
||||||
"#linesTable tbody tr:not(.tax-row)"
|
"#linesTable tbody tr:not(.tax-row)",
|
||||||
);
|
);
|
||||||
rows.forEach((row) => handleTax(row));
|
rows.forEach((row) => handleTax(row));
|
||||||
});
|
});
|
||||||
@@ -452,7 +453,7 @@
|
|||||||
optgroup.label = group.label;
|
optgroup.label = group.label;
|
||||||
|
|
||||||
const groupAccounts = accounts.filter((a) =>
|
const groupAccounts = accounts.filter((a) =>
|
||||||
group.codes.includes(a.account_code)
|
group.codes.includes(a.account_code),
|
||||||
);
|
);
|
||||||
|
|
||||||
groupAccounts.forEach((acc) => {
|
groupAccounts.forEach((acc) => {
|
||||||
@@ -471,7 +472,7 @@
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
function loadDescriptionHistory() {
|
function loadDescriptionHistory() {
|
||||||
const history = JSON.parse(
|
const history = JSON.parse(
|
||||||
localStorage.getItem("descriptionHistory") || "[]"
|
localStorage.getItem("descriptionHistory") || "[]",
|
||||||
);
|
);
|
||||||
const datalist = document.getElementById("descriptionHistory");
|
const datalist = document.getElementById("descriptionHistory");
|
||||||
datalist.innerHTML = "";
|
datalist.innerHTML = "";
|
||||||
@@ -487,7 +488,7 @@
|
|||||||
if (!description || description.trim() === "") return;
|
if (!description || description.trim() === "") return;
|
||||||
|
|
||||||
let history = JSON.parse(
|
let history = JSON.parse(
|
||||||
localStorage.getItem("descriptionHistory") || "[]"
|
localStorage.getItem("descriptionHistory") || "[]",
|
||||||
);
|
);
|
||||||
|
|
||||||
// 重複を避け、先頭に追加
|
// 重複を避け、先頭に追加
|
||||||
@@ -512,7 +513,7 @@
|
|||||||
taxPaidAccounts
|
taxPaidAccounts
|
||||||
.map(
|
.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
|
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
@@ -521,7 +522,7 @@
|
|||||||
taxReceivedAccounts
|
taxReceivedAccounts
|
||||||
.map(
|
.map(
|
||||||
(a) =>
|
(a) =>
|
||||||
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
|
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
@@ -660,7 +661,7 @@
|
|||||||
balanceStatus.style.background = "";
|
balanceStatus.style.background = "";
|
||||||
} else {
|
} else {
|
||||||
balanceStatus.textContent = `✗ 差額: ${Math.abs(
|
balanceStatus.textContent = `✗ 差額: ${Math.abs(
|
||||||
totalDebit - totalCredit
|
totalDebit - totalCredit,
|
||||||
).toLocaleString()}`;
|
).toLocaleString()}`;
|
||||||
balanceStatus.style.background = "#f8d7da";
|
balanceStatus.style.background = "#f8d7da";
|
||||||
balanceStatus.style.color = "#721c24";
|
balanceStatus.style.color = "#721c24";
|
||||||
@@ -677,10 +678,10 @@
|
|||||||
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(
|
const debit = Number(
|
||||||
row.querySelector(".debit").value.replace(/,/g, "") || 0
|
row.querySelector(".debit").value.replace(/,/g, "") || 0,
|
||||||
);
|
);
|
||||||
const credit = Number(
|
const credit = Number(
|
||||||
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
row.querySelector(".credit").value.replace(/,/g, "") || 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (taxType === "none" || taxDir === "none") {
|
if (taxType === "none" || taxDir === "none") {
|
||||||
@@ -722,7 +723,7 @@
|
|||||||
addRow(
|
addRow(
|
||||||
true,
|
true,
|
||||||
{ accountId: taxAcc, amount: taxAmount, side: "debit" },
|
{ accountId: taxAcc, amount: taxAmount, side: "debit" },
|
||||||
parentId
|
parentId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (taxDir === "received") {
|
if (taxDir === "received") {
|
||||||
@@ -734,7 +735,7 @@
|
|||||||
addRow(
|
addRow(
|
||||||
true,
|
true,
|
||||||
{ accountId: taxAcc, amount: taxAmount, side: "credit" },
|
{ accountId: taxAcc, amount: taxAmount, side: "credit" },
|
||||||
parentId
|
parentId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,10 +782,10 @@
|
|||||||
|
|
||||||
const accountId = Number(row.querySelector(".account").value);
|
const accountId = Number(row.querySelector(".account").value);
|
||||||
const debit = Number(
|
const debit = Number(
|
||||||
row.querySelector(".debit").value.replace(/,/g, "") || 0
|
row.querySelector(".debit").value.replace(/,/g, "") || 0,
|
||||||
);
|
);
|
||||||
const credit = Number(
|
const credit = Number(
|
||||||
row.querySelector(".credit").value.replace(/,/g, "") || 0
|
row.querySelector(".credit").value.replace(/,/g, "") || 0,
|
||||||
);
|
);
|
||||||
const taxType = row.querySelector(".taxType").value;
|
const taxType = row.querySelector(".taxType").value;
|
||||||
const taxDirection = row.querySelector(".taxDirection").value;
|
const taxDirection = row.querySelector(".taxDirection").value;
|
||||||
@@ -818,7 +819,7 @@
|
|||||||
// 税科目(仮払消費税等・仮受消費税等)が存在するかチェック
|
// 税科目(仮払消費税等・仮受消費税等)が存在するかチェック
|
||||||
const hasTaxAccount = lines.some((line) => {
|
const hasTaxAccount = lines.some((line) => {
|
||||||
const account = accounts.find(
|
const account = accounts.find(
|
||||||
(a) => a.account_id === line.account_id
|
(a) => a.account_id === line.account_id,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
account &&
|
account &&
|
||||||
@@ -829,7 +830,7 @@
|
|||||||
|
|
||||||
// 税科目が存在し、かつ税情報を持つ行がある場合は警告
|
// 税科目が存在し、かつ税情報を持つ行がある場合は警告
|
||||||
const hasTaxInfo = lines.some(
|
const hasTaxInfo = lines.some(
|
||||||
(line) => line.tax_rate != null && line.tax_direction != null
|
(line) => line.tax_rate != null && line.tax_direction != null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasTaxAccount && hasTaxInfo) {
|
if (hasTaxAccount && hasTaxInfo) {
|
||||||
@@ -838,7 +839,7 @@
|
|||||||
"警告:仮払消費税等・仮受消費税等の行がすでに存在していますが、他の行に税区分・税方向が設定されています。\n" +
|
"警告:仮払消費税等・仮受消費税等の行がすでに存在していますが、他の行に税区分・税方向が設定されています。\n" +
|
||||||
"このまま登録すると税が二重計算され、借貸不平衡になります。\n\n" +
|
"このまま登録すると税が二重計算され、借貸不平衡になります。\n\n" +
|
||||||
"税区分・税方向をすべて「対象外」に設定してから登録することをお勧めします。\n\n" +
|
"税区分・税方向をすべて「対象外」に設定してから登録することをお勧めします。\n\n" +
|
||||||
"このまま登録しますか?"
|
"このまま登録しますか?",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -903,7 +904,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("送信エラー:", error);
|
console.error("送信エラー:", error);
|
||||||
alert(
|
alert(
|
||||||
`エラーが発生しました: ${error.message}\n\nサーバーが起動しているか確認してください。`
|
`エラーが発生しました: ${error.message}\n\nサーバーが起動しているか確認してください。`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -955,7 +956,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await callApi(
|
await callApi(
|
||||||
`/month-locks/unlock?fiscal_year=${year}&month=${month}`
|
`/month-locks/unlock?fiscal_year=${year}&month=${month}`,
|
||||||
);
|
);
|
||||||
el.innerText = `✅ ${year}年${month}月 已解锁`;
|
el.innerText = `✅ ${year}年${month}月 已解锁`;
|
||||||
await refreshMonthLocks(); // 可选:自动刷新列表
|
await refreshMonthLocks(); // 可选:自动刷新列表
|
||||||
@@ -985,8 +986,8 @@
|
|||||||
(x) =>
|
(x) =>
|
||||||
`${x.fiscal_year}-${String(x.month).padStart(
|
`${x.fiscal_year}-${String(x.month).padStart(
|
||||||
2,
|
2,
|
||||||
"0"
|
"0",
|
||||||
)} locked_by=${x.locked_by ?? ""}`
|
)} locked_by=${x.locked_by ?? ""}`,
|
||||||
)
|
)
|
||||||
.join("\n");
|
.join("\n");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1005,7 +1006,7 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
return (data || []).some(
|
return (data || []).some(
|
||||||
(x) => x.fiscal_year === year && x.month === month && x.is_locked
|
(x) => x.fiscal_year === year && x.month === month && x.is_locked,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1049,7 +1050,7 @@
|
|||||||
|
|
||||||
// 最近使った科目を表示
|
// 最近使った科目を表示
|
||||||
const recentAccountIds = JSON.parse(
|
const recentAccountIds = JSON.parse(
|
||||||
localStorage.getItem("recentAccounts") || "[]"
|
localStorage.getItem("recentAccounts") || "[]",
|
||||||
);
|
);
|
||||||
if (recentAccountIds.length > 0) {
|
if (recentAccountIds.length > 0) {
|
||||||
html += `<optgroup label="⭐ 最近使った科目">`;
|
html += `<optgroup label="⭐ 最近使った科目">`;
|
||||||
@@ -1080,7 +1081,7 @@
|
|||||||
// 未分组的其他科目
|
// 未分组的其他科目
|
||||||
const groupedCodes = ACCOUNT_GROUPS.flatMap((g) => g.codes);
|
const groupedCodes = ACCOUNT_GROUPS.flatMap((g) => g.codes);
|
||||||
const others = accounts.filter(
|
const others = accounts.filter(
|
||||||
(a) => !groupedCodes.includes(a.account_code)
|
(a) => !groupedCodes.includes(a.account_code),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (others.length > 0) {
|
if (others.length > 0) {
|
||||||
@@ -1101,7 +1102,7 @@
|
|||||||
if (!accountId) return;
|
if (!accountId) return;
|
||||||
|
|
||||||
let recentAccounts = JSON.parse(
|
let recentAccounts = JSON.parse(
|
||||||
localStorage.getItem("recentAccounts") || "[]"
|
localStorage.getItem("recentAccounts") || "[]",
|
||||||
);
|
);
|
||||||
|
|
||||||
// 重複を除去して先頭に追加
|
// 重複を除去して先頭に追加
|
||||||
@@ -1127,7 +1128,7 @@
|
|||||||
const key = document.getElementById("searchKeyword").value;
|
const key = document.getElementById("searchKeyword").value;
|
||||||
const accountId = document.getElementById("searchAccountId").value;
|
const accountId = document.getElementById("searchAccountId").value;
|
||||||
const includeHistory = document.getElementById(
|
const includeHistory = document.getElementById(
|
||||||
"searchIncludeHistory"
|
"searchIncludeHistory",
|
||||||
).checked;
|
).checked;
|
||||||
|
|
||||||
const qs = new URLSearchParams();
|
const qs = new URLSearchParams();
|
||||||
@@ -1191,7 +1192,7 @@
|
|||||||
{
|
{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const section = document.createElement("div");
|
const section = document.createElement("div");
|
||||||
@@ -1287,7 +1288,7 @@
|
|||||||
const key = document.getElementById("searchKeyword").value;
|
const key = document.getElementById("searchKeyword").value;
|
||||||
const accountId = document.getElementById("searchAccountId").value;
|
const accountId = document.getElementById("searchAccountId").value;
|
||||||
const includeHistory = document.getElementById(
|
const includeHistory = document.getElementById(
|
||||||
"searchIncludeHistory"
|
"searchIncludeHistory",
|
||||||
).checked;
|
).checked;
|
||||||
|
|
||||||
// 検索条件を保存
|
// 検索条件を保存
|
||||||
@@ -1299,7 +1300,7 @@
|
|||||||
keyword: key,
|
keyword: key,
|
||||||
accountId: accountId,
|
accountId: accountId,
|
||||||
includeHistory: includeHistory,
|
includeHistory: includeHistory,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const qs = new URLSearchParams();
|
const qs = new URLSearchParams();
|
||||||
@@ -1358,7 +1359,7 @@
|
|||||||
{
|
{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const section = document.createElement("div");
|
const section = document.createElement("div");
|
||||||
@@ -1512,10 +1513,10 @@
|
|||||||
|
|
||||||
console.log("コピーされたデータ:", data); // デバッグ用
|
console.log("コピーされたデータ:", data); // デバッグ用
|
||||||
console.log(
|
console.log(
|
||||||
`${data.lines.length}行をコピーしました。税区分・税方向はすべて「対象外」に設定されています。`
|
`${data.lines.length}行をコピーしました。税区分・税方向はすべて「対象外」に設定されています。`,
|
||||||
);
|
);
|
||||||
alert(
|
alert(
|
||||||
`仕訳を複製しました(${data.lines.length}行)。\n税の自動計算を防ぐため、すべての行の税区分・税方向を「対象外」に設定しています。\n必要に応じて修正してから登録してください。`
|
`仕訳を複製しました(${data.lines.length}行)。\n税の自動計算を防ぐため、すべての行の税区分・税方向を「対象外」に設定しています。\n必要に応じて修正してから登録してください。`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
@@ -1529,7 +1530,7 @@
|
|||||||
async function reverseAndEdit(id) {
|
async function reverseAndEdit(id) {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?"
|
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -1543,14 +1544,14 @@
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
alert(
|
alert(
|
||||||
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`
|
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
alert(
|
alert(
|
||||||
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`
|
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ② 元仕訳内容を取得して修正画面へ遷移
|
// ② 元仕訳内容を取得して修正画面へ遷移
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>仕訳一覧</title>
|
<title>仕訳一覧</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -63,7 +64,7 @@
|
|||||||
fromDate: from,
|
fromDate: from,
|
||||||
toDate: to,
|
toDate: to,
|
||||||
keyword: key,
|
keyword: key,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const qs = new URLSearchParams();
|
const qs = new URLSearchParams();
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
async function reverseAndEdit(id) {
|
async function reverseAndEdit(id) {
|
||||||
if (
|
if (
|
||||||
!confirm(
|
!confirm(
|
||||||
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?"
|
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -120,14 +121,14 @@
|
|||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
alert(
|
alert(
|
||||||
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`
|
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
alert(
|
alert(
|
||||||
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`
|
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ② 元仕訳内容を取得して修正画面へ遷移
|
// ② 元仕訳内容を取得して修正画面へ遷移
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>仕訳詳細</title>
|
<title>仕訳詳細</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>仕訳入力</title>
|
<title>仕訳入力</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
41
frontend/js/auth.js
Normal file
41
frontend/js/auth.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 认证和会话管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 检查用户是否已登录
|
||||||
|
function checkAuth() {
|
||||||
|
const user = localStorage.getItem("currentUser");
|
||||||
|
if (!user) {
|
||||||
|
// 用户未登录,重定向到登录页面
|
||||||
|
window.location.href = "/login.html";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前登录的用户信息
|
||||||
|
function getCurrentUser() {
|
||||||
|
const user = localStorage.getItem("currentUser");
|
||||||
|
return user ? JSON.parse(user) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存用户信息(登录时使用)
|
||||||
|
function setCurrentUser(userData) {
|
||||||
|
localStorage.setItem("currentUser", JSON.stringify(userData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注销用户
|
||||||
|
function logout() {
|
||||||
|
localStorage.removeItem("currentUser");
|
||||||
|
window.location.href = "/login.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在页面加载时检查认证(仅在非登录页面使用)
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const currentPage = window.location.pathname;
|
||||||
|
|
||||||
|
// 排除登录页面的检查
|
||||||
|
if (!currentPage.includes("login.html")) {
|
||||||
|
checkAuth();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>元帳</title>
|
<title>元帳</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
411
frontend/login.html
Normal file
411
frontend/login.html
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ja">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>NJTS 会計システム - ログイン</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family:
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
"Yu Gothic UI",
|
||||||
|
"Hiragino Kaku Gothic ProN",
|
||||||
|
"メイリオ",
|
||||||
|
sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="password"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-register {
|
||||||
|
background: transparent;
|
||||||
|
color: #667eea;
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-register:hover {
|
||||||
|
background: #f0f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.error {
|
||||||
|
background: #fee;
|
||||||
|
color: #c33;
|
||||||
|
border: 1px solid #fcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.success {
|
||||||
|
background: #efe;
|
||||||
|
color: #3c3;
|
||||||
|
border: 1px solid #cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-form {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-form a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-form a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: none;
|
||||||
|
text-align: center;
|
||||||
|
color: #667eea;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="logo">
|
||||||
|
<h1>NJTS</h1>
|
||||||
|
<p>会計システム</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ログインフォーム -->
|
||||||
|
<div id="loginForm" class="form active">
|
||||||
|
<div id="messageLogin" class="message"></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginUsername">ユーザー名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="loginUsername"
|
||||||
|
placeholder="ユーザー名を入力"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginPassword">パスワード</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="loginPassword"
|
||||||
|
placeholder="パスワードを入力"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn-login" onclick="handleLogin()">ログイン</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-form">
|
||||||
|
新規登録?
|
||||||
|
<a onclick="toggleForm()">アカウント作成</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="loading" id="loginLoading">処理中...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 登録フォーム -->
|
||||||
|
<div id="registerForm" class="form">
|
||||||
|
<div id="messageRegister" class="message"></div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="regUsername">ユーザー名</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="regUsername"
|
||||||
|
placeholder="ユーザー名を入力"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="regPassword">パスワード</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="regPassword"
|
||||||
|
placeholder="6文字以上で設定"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button class="btn-login" onclick="handleRegister()">登録</button>
|
||||||
|
<button class="btn-register" onclick="toggleForm()">
|
||||||
|
キャンセル
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="loading" id="registerLoading">処理中...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleForm() {
|
||||||
|
const loginForm = document.getElementById("loginForm");
|
||||||
|
const registerForm = document.getElementById("registerForm");
|
||||||
|
|
||||||
|
loginForm.classList.toggle("active");
|
||||||
|
registerForm.classList.toggle("active");
|
||||||
|
|
||||||
|
// メッセージをリセット
|
||||||
|
clearMessage("messageLogin");
|
||||||
|
clearMessage("messageRegister");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMessage(elementId, message, isError = false) {
|
||||||
|
const messageEl = document.getElementById(elementId);
|
||||||
|
messageEl.textContent = message;
|
||||||
|
messageEl.className = `message ${isError ? "error" : "success"}`;
|
||||||
|
messageEl.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMessage(elementId) {
|
||||||
|
const messageEl = document.getElementById(elementId);
|
||||||
|
messageEl.style.display = "none";
|
||||||
|
messageEl.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
const username = document.getElementById("loginUsername").value;
|
||||||
|
const password = document.getElementById("loginPassword").value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
showMessage(
|
||||||
|
"messageLogin",
|
||||||
|
"ユーザー名とパスワードを入力してください",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingEl = document.getElementById("loginLoading");
|
||||||
|
loadingEl.style.display = "block";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/users/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
// ユーザー情報をlocalStorageに保存
|
||||||
|
localStorage.setItem("currentUser", JSON.stringify(data));
|
||||||
|
showMessage("messageLogin", "ログインに成功しました!", false);
|
||||||
|
// 2秒後にメインページにリダイレクト
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/index.html";
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
showMessage(
|
||||||
|
"messageLogin",
|
||||||
|
data.detail || "ログインに失敗しました",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage(
|
||||||
|
"messageLogin",
|
||||||
|
"エラーが発生しました: " + error.message,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
loadingEl.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
const username = document.getElementById("regUsername").value;
|
||||||
|
const password = document.getElementById("regPassword").value;
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
showMessage(
|
||||||
|
"messageRegister",
|
||||||
|
"ユーザー名とパスワードを入力してください",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
showMessage(
|
||||||
|
"messageRegister",
|
||||||
|
"パスワードは6文字以上である必要があります",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingEl = document.getElementById("registerLoading");
|
||||||
|
loadingEl.style.display = "block";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/users/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username, password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showMessage(
|
||||||
|
"messageRegister",
|
||||||
|
"登録が完了しました。ログインしてください",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
toggleForm();
|
||||||
|
document.getElementById("loginUsername").value = username;
|
||||||
|
document.getElementById("loginPassword").value = "";
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
showMessage(
|
||||||
|
"messageRegister",
|
||||||
|
data.detail || "登録に失敗しました",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage(
|
||||||
|
"messageRegister",
|
||||||
|
"エラーが発生しました: " + error.message,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
loadingEl.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enterキーでログイン/登録
|
||||||
|
document.addEventListener("keypress", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
const loginForm = document.getElementById("loginForm");
|
||||||
|
if (loginForm.classList.contains("active")) {
|
||||||
|
handleLogin();
|
||||||
|
} else {
|
||||||
|
handleRegister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>期首残高入力</title>
|
<title>期首残高入力</title>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<button onclick="saveOpeningBalances()">保存</button>
|
<button onclick="saveOpeningBalances()">保存</button>
|
||||||
<button onclick="location.href='trial-balance.html'">← 戻る</button>
|
<button onclick="location.href = 'trial-balance.html'">← 戻る</button>
|
||||||
|
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
<script src="js/opening_balance.js"></script>
|
<script src="js/opening_balance.js"></script>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>給与管理 - 月次給与計算</title>
|
<title>給与管理 - 月次給与計算</title>
|
||||||
<link rel="stylesheet" href="/css/style.css" />
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>給与管理 - 従業員管理</title>
|
<title>給与管理 - 従業員管理</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>給与管理 - 給与設定</title>
|
<title>給与管理 - 給与設定</title>
|
||||||
<link rel="stylesheet" href="/css/style.css" />
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
@@ -213,7 +214,7 @@
|
|||||||
async function loadEmployees() {
|
async function loadEmployees() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/employees/?is_active=true`
|
`${API_BASE}/payroll/employees/?is_active=true`,
|
||||||
);
|
);
|
||||||
employees = await response.json();
|
employees = await response.json();
|
||||||
|
|
||||||
@@ -223,7 +224,7 @@
|
|||||||
employees
|
employees
|
||||||
.map(
|
.map(
|
||||||
(emp) =>
|
(emp) =>
|
||||||
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`
|
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
@@ -241,7 +242,7 @@
|
|||||||
for (const emp of employees) {
|
for (const emp of employees) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/salary/${emp.employee_id}/current`
|
`${API_BASE}/payroll/settings/salary/${emp.employee_id}/current`,
|
||||||
);
|
);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const setting = await response.json();
|
const setting = await response.json();
|
||||||
@@ -255,7 +256,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`従業員 ${emp.employee_id} の給与設定取得失敗`,
|
`従業員 ${emp.employee_id} の給与設定取得失敗`,
|
||||||
error
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +317,7 @@
|
|||||||
})" style="padding: 5px 10px; font-size: 12px;">編集</button>
|
})" style="padding: 5px 10px; font-size: 12px;">編集</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("")
|
.join("")
|
||||||
: '<tr><td colspan="11" style="text-align:center;">給与設定が登録されていません</td></tr>'
|
: '<tr><td colspan="11" style="text-align:center;">給与設定が登録されていません</td></tr>'
|
||||||
@@ -334,7 +335,7 @@
|
|||||||
// 全従業員の設定を取得して該当するものを探す
|
// 全従業員の設定を取得して該当するものを探す
|
||||||
for (const emp of employees) {
|
for (const emp of employees) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/salary/${emp.employee_id}`
|
`${API_BASE}/payroll/settings/salary/${emp.employee_id}`,
|
||||||
);
|
);
|
||||||
const settings = await response.json();
|
const settings = await response.json();
|
||||||
const setting = settings.find((s) => s.setting_id === settingId);
|
const setting = settings.find((s) => s.setting_id === settingId);
|
||||||
@@ -408,7 +409,7 @@
|
|||||||
commute_allowance: parseFloat(formData.get("commute_allowance")) || 0,
|
commute_allowance: parseFloat(formData.get("commute_allowance")) || 0,
|
||||||
other_allowance: parseFloat(formData.get("other_allowance")) || 0,
|
other_allowance: parseFloat(formData.get("other_allowance")) || 0,
|
||||||
employment_insurance_eligible: document.getElementById(
|
employment_insurance_eligible: document.getElementById(
|
||||||
"employment_insurance_eligible"
|
"employment_insurance_eligible",
|
||||||
).checked,
|
).checked,
|
||||||
valid_from: formData.get("valid_from"),
|
valid_from: formData.get("valid_from"),
|
||||||
};
|
};
|
||||||
@@ -431,7 +432,7 @@
|
|||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 新規追加
|
// 新規追加
|
||||||
@@ -444,7 +445,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(
|
alert(
|
||||||
settingId ? "給与設定を更新しました" : "給与設定を保存しました"
|
settingId ? "給与設定を更新しました" : "給与設定を保存しました",
|
||||||
);
|
);
|
||||||
hideAddForm();
|
hideAddForm();
|
||||||
loadAllSalarySettings();
|
loadAllSalarySettings();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>給与管理 - 設定</title>
|
<title>給与管理 - 設定</title>
|
||||||
<link rel="stylesheet" href="/css/style.css" />
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
@@ -651,7 +652,7 @@
|
|||||||
async function loadStdRemunerationYears() {
|
async function loadStdRemunerationYears() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/standard-remuneration/years`
|
`${API_BASE}/payroll/settings/standard-remuneration/years`,
|
||||||
);
|
);
|
||||||
const years = await response.json();
|
const years = await response.json();
|
||||||
|
|
||||||
@@ -678,7 +679,7 @@
|
|||||||
<div id="std-table-${year}">読み込み中...</div>
|
<div id="std-table-${year}">読み込み中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
@@ -729,7 +730,7 @@
|
|||||||
|
|
||||||
// セッションストレージからメタデータを取得
|
// セッションストレージからメタデータを取得
|
||||||
const metadata = JSON.parse(
|
const metadata = JSON.parse(
|
||||||
sessionStorage.getItem("stdRemunMetadata") || "{}"
|
sessionStorage.getItem("stdRemunMetadata") || "{}",
|
||||||
);
|
);
|
||||||
console.log("取得したメタデータ:", metadata);
|
console.log("取得したメタデータ:", metadata);
|
||||||
|
|
||||||
@@ -752,9 +753,8 @@
|
|||||||
document.getElementById(`std-table-${year}`).innerHTML = tablesHtml;
|
document.getElementById(`std-table-${year}`).innerHTML = tablesHtml;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("データ読み込みエラー:", error);
|
console.error("データ読み込みエラー:", error);
|
||||||
document.getElementById(
|
document.getElementById(`std-table-${year}`).innerHTML =
|
||||||
`std-table-${year}`
|
`<p style="color:red;">データの取得に失敗しました: ${error.message}</p>`;
|
||||||
).innerHTML = `<p style="color:red;">データの取得に失敗しました: ${error.message}</p>`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,7 +768,7 @@
|
|||||||
console.log("renderStdRemunerationTable - metadata内容:", metadata);
|
console.log("renderStdRemunerationTable - metadata内容:", metadata);
|
||||||
console.log(
|
console.log(
|
||||||
"renderStdRemunerationTable - metadata.source:",
|
"renderStdRemunerationTable - metadata.source:",
|
||||||
metadata.source
|
metadata.source,
|
||||||
);
|
);
|
||||||
if (metadata.source) {
|
if (metadata.source) {
|
||||||
sourceHtml = `<p style="margin-bottom: 15px; padding: 10px; background: #f0f8ff; border-left: 3px solid #0066cc; color: #333;"><strong>対象期間:</strong> ${metadata.source}</p>`;
|
sourceHtml = `<p style="margin-bottom: 15px; padding: 10px; background: #f0f8ff; border-left: 3px solid #0066cc; color: #333;"><strong>対象期間:</strong> ${metadata.source}</p>`;
|
||||||
@@ -831,7 +831,7 @@
|
|||||||
step1Data.map((d) => ({
|
step1Data.map((d) => ({
|
||||||
grade: d.grade,
|
grade: d.grade,
|
||||||
pension: d.pension_insurance,
|
pension: d.pension_insurance,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
// ステップ2: 非ゼロ値を下方へ延続(前の有効値が0以下になったら、前の非ゼロ値を使用)
|
// ステップ2: 非ゼロ値を下方へ延続(前の有効値が0以下になったら、前の非ゼロ値を使用)
|
||||||
@@ -856,7 +856,7 @@
|
|||||||
adjustedData.map((d) => ({
|
adjustedData.map((d) => ({
|
||||||
grade: d.grade,
|
grade: d.grade,
|
||||||
pension: d.pension_insurance,
|
pension: d.pension_insurance,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("調整後のデータ(最初の3件):", adjustedData.slice(0, 3));
|
console.log("調整後のデータ(最初の3件):", adjustedData.slice(0, 3));
|
||||||
@@ -927,15 +927,15 @@
|
|||||||
<td>${Number(row.salary_from ?? 0).toLocaleString()}</td>
|
<td>${Number(row.salary_from ?? 0).toLocaleString()}</td>
|
||||||
<td>${Number(row.salary_to ?? 0).toLocaleString()}</td>
|
<td>${Number(row.salary_to ?? 0).toLocaleString()}</td>
|
||||||
<td>${Number(
|
<td>${Number(
|
||||||
row.health_insurance_no_care ?? 0
|
row.health_insurance_no_care ?? 0,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>${Number(healthNoCareHalf ?? 0).toLocaleString()}</td>
|
<td>${Number(healthNoCareHalf ?? 0).toLocaleString()}</td>
|
||||||
<td>${Number(
|
<td>${Number(
|
||||||
row.health_insurance_with_care ?? 0
|
row.health_insurance_with_care ?? 0,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>${Number(healthWithCareHalf ?? 0).toLocaleString()}</td>
|
<td>${Number(healthWithCareHalf ?? 0).toLocaleString()}</td>
|
||||||
<td>${Number(
|
<td>${Number(
|
||||||
row.pension_insurance ?? 0
|
row.pension_insurance ?? 0,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>${Number(pensionHalf ?? 0).toLocaleString()}</td>
|
<td>${Number(pensionHalf ?? 0).toLocaleString()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -958,7 +958,7 @@
|
|||||||
|
|
||||||
async function deleteStdRemunerationYear(year) {
|
async function deleteStdRemunerationYear(year) {
|
||||||
const confirmed = confirm(
|
const confirmed = confirm(
|
||||||
`⚠️ 警告\n\n${year}年度の標準報酬月額表データを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?`
|
`⚠️ 警告\n\n${year}年度の標準報酬月額表データを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?`,
|
||||||
);
|
);
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
console.log(`${year}年度の削除がキャンセルされました`);
|
console.log(`${year}年度の削除がキャンセルされました`);
|
||||||
@@ -970,7 +970,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/standard-remuneration/year/${year}`,
|
`${API_BASE}/payroll/settings/standard-remuneration/year/${year}`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("削除レスポンスステータス:", response.status);
|
console.log("削除レスポンスステータス:", response.status);
|
||||||
@@ -979,7 +979,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(
|
alert(
|
||||||
`✓ ${year}年度のデータ(${result.deleted_count}件)を削除しました`
|
`✓ ${year}年度のデータ(${result.deleted_count}件)を削除しました`,
|
||||||
);
|
);
|
||||||
console.log(`=== ${year}年度データ削除完了 ===`);
|
console.log(`=== ${year}年度データ削除完了 ===`);
|
||||||
|
|
||||||
@@ -987,7 +987,7 @@
|
|||||||
await loadStdRemunerationYears();
|
await loadStdRemunerationYears();
|
||||||
} else {
|
} else {
|
||||||
alert(
|
alert(
|
||||||
"削除に失敗しました:\n" + JSON.stringify(result.detail || result)
|
"削除に失敗しました:\n" + JSON.stringify(result.detail || result),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -998,7 +998,7 @@
|
|||||||
|
|
||||||
async function clearAllStdRemunerationData() {
|
async function clearAllStdRemunerationData() {
|
||||||
const confirmed = confirm(
|
const confirmed = confirm(
|
||||||
"⚠️ 警告\n\n標準報酬月額表のすべてのデータを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?"
|
"⚠️ 警告\n\n標準報酬月額表のすべてのデータを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?",
|
||||||
);
|
);
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
console.log("削除操作がキャンセルされました");
|
console.log("削除操作がキャンセルされました");
|
||||||
@@ -1010,7 +1010,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/standard-remuneration/all`,
|
`${API_BASE}/payroll/settings/standard-remuneration/all`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("削除レスポンスステータス:", response.status);
|
console.log("削除レスポンスステータス:", response.status);
|
||||||
@@ -1019,7 +1019,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(
|
alert(
|
||||||
`✓ ${result.deleted_count}件のデータを削除しました\n\n再度Excelファイルをインポートしてください`
|
`✓ ${result.deleted_count}件のデータを削除しました\n\n再度Excelファイルをインポートしてください`,
|
||||||
);
|
);
|
||||||
console.log("=== 全データ削除完了 ===");
|
console.log("=== 全データ削除完了 ===");
|
||||||
|
|
||||||
@@ -1028,7 +1028,7 @@
|
|||||||
"<p>データが削除されました。Excelファイルを再度インポートしてください。</p>";
|
"<p>データが削除されました。Excelファイルを再度インポートしてください。</p>";
|
||||||
} else {
|
} else {
|
||||||
alert(
|
alert(
|
||||||
"削除に失敗しました:\n" + JSON.stringify(result.detail || result)
|
"削除に失敗しました:\n" + JSON.stringify(result.detail || result),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1150,7 +1150,7 @@
|
|||||||
async function editInsuranceRate(rateId) {
|
async function editInsuranceRate(rateId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-rates`
|
`${API_BASE}/payroll/settings/insurance-rates`,
|
||||||
);
|
);
|
||||||
const rates = await response.json();
|
const rates = await response.json();
|
||||||
const rate = rates.find((r) => r.rate_id === rateId);
|
const rate = rates.find((r) => r.rate_id === rateId);
|
||||||
@@ -1184,7 +1184,7 @@
|
|||||||
async function copyInsuranceRate(rateId) {
|
async function copyInsuranceRate(rateId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-rates`
|
`${API_BASE}/payroll/settings/insurance-rates`,
|
||||||
);
|
);
|
||||||
const rates = await response.json();
|
const rates = await response.json();
|
||||||
const rate = rates.find((r) => r.rate_id === rateId);
|
const rate = rates.find((r) => r.rate_id === rateId);
|
||||||
@@ -1265,7 +1265,7 @@
|
|||||||
// 介護保険年齢を数値に変換(空の場合はnull)
|
// 介護保険年齢を数値に変換(空の場合はnull)
|
||||||
if (data.care_insurance_age_threshold) {
|
if (data.care_insurance_age_threshold) {
|
||||||
data.care_insurance_age_threshold = parseInt(
|
data.care_insurance_age_threshold = parseInt(
|
||||||
data.care_insurance_age_threshold
|
data.care_insurance_age_threshold,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
data.care_insurance_age_threshold = null;
|
data.care_insurance_age_threshold = null;
|
||||||
@@ -1278,7 +1278,7 @@
|
|||||||
"送信するURL:",
|
"送信するURL:",
|
||||||
isEditMode
|
isEditMode
|
||||||
? `${API_BASE}/payroll/settings/insurance-rates/${rateId}`
|
? `${API_BASE}/payroll/settings/insurance-rates/${rateId}`
|
||||||
: `${API_BASE}/payroll/settings/insurance-rates`
|
: `${API_BASE}/payroll/settings/insurance-rates`,
|
||||||
);
|
);
|
||||||
console.log("送信するデータ:", JSON.stringify(data, null, 2));
|
console.log("送信するデータ:", JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
@@ -1302,7 +1302,7 @@
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log("成功レスポンス:", result);
|
console.log("成功レスポンス:", result);
|
||||||
alert(
|
alert(
|
||||||
isEditMode ? "保険料率を更新しました" : "保険料率を登録しました"
|
isEditMode ? "保険料率を更新しました" : "保険料率を登録しました",
|
||||||
);
|
);
|
||||||
hideInsuranceForm();
|
hideInsuranceForm();
|
||||||
loadInsuranceRates();
|
loadInsuranceRates();
|
||||||
@@ -1312,7 +1312,7 @@
|
|||||||
alert(
|
alert(
|
||||||
(isEditMode ? "更新" : "登録") +
|
(isEditMode ? "更新" : "登録") +
|
||||||
"に失敗しました: " +
|
"に失敗しました: " +
|
||||||
JSON.stringify(error.detail)
|
JSON.stringify(error.detail),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1328,7 +1328,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-rates/${rateId}`,
|
`${API_BASE}/payroll/settings/insurance-rates/${rateId}`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1362,7 +1362,7 @@
|
|||||||
async function loadChildSupportRates() {
|
async function loadChildSupportRates() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/child-support-rates`
|
`${API_BASE}/payroll/settings/child-support-rates`,
|
||||||
);
|
);
|
||||||
const rates = await response.json();
|
const rates = await response.json();
|
||||||
|
|
||||||
@@ -1390,10 +1390,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>${rate.rate_year}</td>
|
<td>${rate.rate_year}</td>
|
||||||
<td>¥${Number(
|
<td>¥${Number(
|
||||||
rate.income_threshold
|
rate.income_threshold,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>${(parseFloat(rate.contribution_rate) * 100).toFixed(
|
<td>${(parseFloat(rate.contribution_rate) * 100).toFixed(
|
||||||
3
|
3,
|
||||||
)}%</td>
|
)}%</td>
|
||||||
<td>${rate.notes || "-"}</td>
|
<td>${rate.notes || "-"}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -1405,7 +1405,7 @@
|
|||||||
})">削除</button>
|
})">削除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -1422,7 +1422,7 @@
|
|||||||
async function editChildSupportRate(contributionId) {
|
async function editChildSupportRate(contributionId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/child-support-rates`
|
`${API_BASE}/payroll/settings/child-support-rates`,
|
||||||
);
|
);
|
||||||
const rates = await response.json();
|
const rates = await response.json();
|
||||||
const rate = rates.find((r) => r.contribution_id === contributionId);
|
const rate = rates.find((r) => r.contribution_id === contributionId);
|
||||||
@@ -1493,7 +1493,7 @@
|
|||||||
alert(
|
alert(
|
||||||
isEditMode
|
isEditMode
|
||||||
? "子ども・子育て拠出金率を更新しました"
|
? "子ども・子育て拠出金率を更新しました"
|
||||||
: "子ども・子育て拠出金率を登録しました"
|
: "子ども・子育て拠出金率を登録しました",
|
||||||
);
|
);
|
||||||
hideChildSupportForm();
|
hideChildSupportForm();
|
||||||
loadChildSupportRates();
|
loadChildSupportRates();
|
||||||
@@ -1502,7 +1502,7 @@
|
|||||||
alert(
|
alert(
|
||||||
(isEditMode ? "更新" : "登録") +
|
(isEditMode ? "更新" : "登録") +
|
||||||
"に失敗しました: " +
|
"に失敗しました: " +
|
||||||
JSON.stringify(error.detail)
|
JSON.stringify(error.detail),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1517,7 +1517,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/child-support-rates/${contributionId}`,
|
`${API_BASE}/payroll/settings/child-support-rates/${contributionId}`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1550,7 +1550,7 @@
|
|||||||
async function loadLimitSettings() {
|
async function loadLimitSettings() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-limits`
|
`${API_BASE}/payroll/settings/insurance-limits`,
|
||||||
);
|
);
|
||||||
const limits = await response.json();
|
const limits = await response.json();
|
||||||
|
|
||||||
@@ -1591,7 +1591,7 @@
|
|||||||
})">削除</button>
|
})">削除</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -1608,7 +1608,7 @@
|
|||||||
async function editLimitSetting(limitId) {
|
async function editLimitSetting(limitId) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-limits`
|
`${API_BASE}/payroll/settings/insurance-limits`,
|
||||||
);
|
);
|
||||||
const limits = await response.json();
|
const limits = await response.json();
|
||||||
const limit = limits.find((l) => l.limit_id === limitId);
|
const limit = limits.find((l) => l.limit_id === limitId);
|
||||||
@@ -1676,7 +1676,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(
|
alert(
|
||||||
isEditMode ? "上限設定を更新しました" : "上限設定を登録しました"
|
isEditMode ? "上限設定を更新しました" : "上限設定を登録しました",
|
||||||
);
|
);
|
||||||
hideLimitForm();
|
hideLimitForm();
|
||||||
loadLimitSettings();
|
loadLimitSettings();
|
||||||
@@ -1685,7 +1685,7 @@
|
|||||||
alert(
|
alert(
|
||||||
(isEditMode ? "更新" : "登録") +
|
(isEditMode ? "更新" : "登録") +
|
||||||
"に失敗しました: " +
|
"に失敗しました: " +
|
||||||
JSON.stringify(error.detail)
|
JSON.stringify(error.detail),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1700,7 +1700,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/insurance-limits/${limitId}`,
|
`${API_BASE}/payroll/settings/insurance-limits/${limitId}`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -1732,7 +1732,7 @@
|
|||||||
async function loadIncomeTaxYears() {
|
async function loadIncomeTaxYears() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/income-tax/years`
|
`${API_BASE}/payroll/settings/income-tax/years`,
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
@@ -1772,7 +1772,7 @@
|
|||||||
<div id="table-${year}">読み込み中...</div>
|
<div id="table-${year}">読み込み中...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
@@ -1785,7 +1785,7 @@
|
|||||||
|
|
||||||
async function deleteTaxDataYear(year) {
|
async function deleteTaxDataYear(year) {
|
||||||
const confirmed = confirm(
|
const confirmed = confirm(
|
||||||
`⚠️ 警告\n\n${year}年度の所得税率表データを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?`
|
`⚠️ 警告\n\n${year}年度の所得税率表データを削除します。\nこの操作は取り消せません。\n\n本当に削除してもよろしいですか?`,
|
||||||
);
|
);
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
console.log(`${year}年度の削除がキャンセルされました`);
|
console.log(`${year}年度の削除がキャンセルされました`);
|
||||||
@@ -1797,7 +1797,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/income-tax/year/${year}`,
|
`${API_BASE}/payroll/settings/income-tax/year/${year}`,
|
||||||
{ method: "DELETE" }
|
{ method: "DELETE" },
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("削除レスポンスステータス:", response.status);
|
console.log("削除レスポンスステータス:", response.status);
|
||||||
@@ -1806,7 +1806,7 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(
|
alert(
|
||||||
`✓ ${year}年度のデータ(${result.deleted_count}件)を削除しました`
|
`✓ ${year}年度のデータ(${result.deleted_count}件)を削除しました`,
|
||||||
);
|
);
|
||||||
console.log(`=== ${year}年度所得税データ削除完了 ===`);
|
console.log(`=== ${year}年度所得税データ削除完了 ===`);
|
||||||
|
|
||||||
@@ -1814,7 +1814,7 @@
|
|||||||
await loadIncomeTaxYears();
|
await loadIncomeTaxYears();
|
||||||
} else {
|
} else {
|
||||||
alert(
|
alert(
|
||||||
"削除に失敗しました:\n" + JSON.stringify(result.detail || result)
|
"削除に失敗しました:\n" + JSON.stringify(result.detail || result),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1851,7 +1851,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${API_BASE}/payroll/settings/income-tax?${params}`
|
`${API_BASE}/payroll/settings/income-tax?${params}`,
|
||||||
);
|
);
|
||||||
const taxes = await response.json();
|
const taxes = await response.json();
|
||||||
|
|
||||||
@@ -1879,13 +1879,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>${tax.dependents_count}人</td>
|
<td>${tax.dependents_count}人</td>
|
||||||
<td>¥${Number(
|
<td>¥${Number(
|
||||||
tax.monthly_income_from
|
tax.monthly_income_from,
|
||||||
).toLocaleString()}</td>
|
).toLocaleString()}</td>
|
||||||
<td>¥${Number(tax.monthly_income_to).toLocaleString()}</td>
|
<td>¥${Number(tax.monthly_income_to).toLocaleString()}</td>
|
||||||
<td>¥${Number(tax.tax_amount).toLocaleString()}</td>
|
<td>¥${Number(tax.tax_amount).toLocaleString()}</td>
|
||||||
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`
|
`,
|
||||||
)
|
)
|
||||||
.join("")}
|
.join("")}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -1908,7 +1908,7 @@
|
|||||||
|
|
||||||
const taxYear = prompt(
|
const taxYear = prompt(
|
||||||
"税年度を入力してください(空欄の場合はファイルから自動抽出):",
|
"税年度を入力してください(空欄の場合はファイルから自動抽出):",
|
||||||
""
|
"",
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1948,7 +1948,7 @@
|
|||||||
// 都道府県をプロンプトで取得
|
// 都道府県をプロンプトで取得
|
||||||
const prefecture = prompt(
|
const prefecture = prompt(
|
||||||
"都道府県を入力してください(例: 東京、神奈川):",
|
"都道府県を入力してください(例: 東京、神奈川):",
|
||||||
""
|
"",
|
||||||
);
|
);
|
||||||
if (!prefecture || !prefecture.trim()) {
|
if (!prefecture || !prefecture.trim()) {
|
||||||
alert("都道府県を入力してください");
|
alert("都道府県を入力してください");
|
||||||
@@ -1970,7 +1970,7 @@
|
|||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData,
|
body: formData,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("レスポンスステータス:", response.status);
|
console.log("レスポンスステータス:", response.status);
|
||||||
@@ -1990,7 +1990,7 @@
|
|||||||
console.log("メタデータ保存:", result.metadata);
|
console.log("メタデータ保存:", result.metadata);
|
||||||
sessionStorage.setItem(
|
sessionStorage.setItem(
|
||||||
"stdRemunMetadata",
|
"stdRemunMetadata",
|
||||||
JSON.stringify(result.metadata)
|
JSON.stringify(result.metadata),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2009,7 +2009,7 @@
|
|||||||
} else if (typeof errorDetail === "object") {
|
} else if (typeof errorDetail === "object") {
|
||||||
errorMessage += JSON.stringify(errorDetail, null, 2).substring(
|
errorMessage += JSON.stringify(errorDetail, null, 2).substring(
|
||||||
0,
|
0,
|
||||||
1000
|
1000,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
errorMessage += String(errorDetail).substring(0, 500);
|
errorMessage += String(errorDetail).substring(0, 500);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>給与管理システム</title>
|
<title>給与管理システム</title>
|
||||||
<link rel="stylesheet" href="/css/style.css" />
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
@@ -60,7 +61,10 @@
|
|||||||
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
||||||
|
|
||||||
<div class="menu-grid">
|
<div class="menu-grid">
|
||||||
<div class="menu-card" onclick="location.href='payroll-employees.html'">
|
<div
|
||||||
|
class="menu-card"
|
||||||
|
onclick="location.href = 'payroll-employees.html'"
|
||||||
|
>
|
||||||
<div class="icon">👥</div>
|
<div class="icon">👥</div>
|
||||||
<h2>従業員管理</h2>
|
<h2>従業員管理</h2>
|
||||||
<p>従業員の基本情報、扶養家族情報を管理します</p>
|
<p>従業員の基本情報、扶養家族情報を管理します</p>
|
||||||
@@ -68,7 +72,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="menu-card"
|
class="menu-card"
|
||||||
onclick="location.href='payroll-salary-settings.html'"
|
onclick="location.href = 'payroll-salary-settings.html'"
|
||||||
>
|
>
|
||||||
<div class="icon">💰</div>
|
<div class="icon">💰</div>
|
||||||
<h2>給与設定</h2>
|
<h2>給与設定</h2>
|
||||||
@@ -77,14 +81,17 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="menu-card"
|
class="menu-card"
|
||||||
onclick="location.href='payroll-calculation.html'"
|
onclick="location.href = 'payroll-calculation.html'"
|
||||||
>
|
>
|
||||||
<div class="icon">🧮</div>
|
<div class="icon">🧮</div>
|
||||||
<h2>月次給与計算</h2>
|
<h2>月次給与計算</h2>
|
||||||
<p>毎月の給与計算、給与明細の作成・承認を行います</p>
|
<p>毎月の給与計算、給与明細の作成・承認を行います</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-card" onclick="location.href='payroll-settings.html'">
|
<div
|
||||||
|
class="menu-card"
|
||||||
|
onclick="location.href = 'payroll-settings.html'"
|
||||||
|
>
|
||||||
<div class="icon">⚙️</div>
|
<div class="icon">⚙️</div>
|
||||||
<h2>税率・保険料設定</h2>
|
<h2>税率・保険料設定</h2>
|
||||||
<p>社会保険料率、所得税率表の管理を行います</p>
|
<p>社会保険料率、所得税率表の管理を行います</p>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>試算表</title>
|
<title>試算表</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<html lang="ja">
|
<html lang="ja">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<script src="js/auth.js"></script>
|
||||||
<title>账票出力 - UI テスト</title>
|
<title>账票出力 - UI テスト</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
|
|||||||
Reference in New Issue
Block a user