"use strict"; const fs = require("node:fs"); const path = require("node:path"); function parseCsv(text) { const rows = []; let row = [], field = "", inQuotes = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (inQuotes) { if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++; } else inQuotes = false; } else field += c; } else { if (c === '"') inQuotes = true; else if (c === ",") { row.push(field); field = ""; } else if (c === "\r") { /* skip */ } else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; } else field += c; } } if (field.length > 0 || row.length > 0) { row.push(field); rows.push(row); } const headers = rows.shift() || []; return rows.filter((r) => !(r.length === 1 && r[0] === "")).map((r) => { const obj = {}; headers.forEach((h, idx) => { obj[h] = r[idx] !== undefined ? r[idx] : ""; }); return obj; }); } function readCsv(filePath) { let text = fs.readFileSync(filePath, "utf8"); if (text.charCodeAt(0) === 0xfeff) text = text.slice(1); return parseCsv(text); } function csvEscape(s) { return '"' + String(s == null ? "" : s).replace(/"/g, '""') + '"'; } function toCsv(rows, headers) { const lines = [headers.map(csvEscape).join(",")]; for (const r of rows) lines.push(headers.map((h) => csvEscape(r[h])).join(",")); return lines.join("\r\n"); } const sampleDir = path.join(__dirname, "..", "sample"); const outDir = path.join(__dirname, "..", "data", "test-output"); // ---- 組織 ---- const deptRows = readCsv(path.join(sampleDir, "組織_2026_09_03 13_53_35.csv")); const deptOut = deptRows.map((d) => ({ 組織ID: d["組織ID"], 組織名: d["組織名"], 組織コード: d["組織コード"], 説明: d["説明"], })); const deptCsv = toCsv(deptOut, ["組織ID", "組織名", "組織コード", "説明"]); fs.writeFileSync(path.join(outDir, "mock_import_depts.csv"), deptCsv, "utf8"); console.log("組織: ", deptOut.length, "件"); // ---- ユーザー ---- const userRows = readCsv(path.join(sampleDir, "ユーザ_2026_09_03 13_53_13.csv")); const userHeaders = [ "ユーザID", "ログインID", "名前", "ユーザコード", "生年月日", "性別", "言語", "タイムゾーン", "組織コード", "組織", "管理者", "テーマ", "説明", "最終ログイン日時", "パスワード有効期限", "パスワード変更日時", "ログイン回数", "ログイン失敗回数", "テナント管理者", "サイトトップへの作成を許可", "グループの管理を許可", "グループの作成を許可", "APIを許可", "サイトトップからの移動を許可", "2段階認証の有効化", "2段階認証の無効化", "無効", "ロック", "ロックカウンター", "秘密鍵有効", "ログイン有効期限", "無効化までの日数", "メールアドレス", "パスワード", ]; const userOut = userRows.map((u) => { const o = {}; for (const h of userHeaders) o[h] = u[h]; return o; }); const userCsv = toCsv(userOut, userHeaders); fs.writeFileSync(path.join(outDir, "mock_import_users.csv"), userCsv, "utf8"); console.log("ユーザー: ", userOut.length, "件"); // ---- グループ(1571行を59グループへ集約、メンバーはDescription列にテキスト格納) ---- const groupRows = readCsv(path.join(sampleDir, "グループ_2026_09_03 13_53_26.csv")); const groupsMap = new Map(); for (const row of groupRows) { const gid = row["グループID"]; if (!groupsMap.has(gid)) { groupsMap.set(gid, { グループID: gid, グループ名: row["グループ名"], 説明: row["説明"], members: [], }); } const g = groupsMap.get(gid); if (row["メンバー種別"] && row["メンバーキー"]) { g.members.push(`${row["メンバー種別"]},${row["メンバーキー"]},${row["メンバーは管理者"] === "1" ? "True" : "False"}`); } } const groupOut = [...groupsMap.values()].map((g) => ({ グループID: g.グループID, グループ名: g.グループ名, 説明: g.説明, "グループメンバー(種別,キー,管理者フラグ 1行1メンバー)": g.members.join("\n"), })); const groupCsv = toCsv(groupOut, ["グループID", "グループ名", "説明", "グループメンバー(種別,キー,管理者フラグ 1行1メンバー)"]); fs.writeFileSync(path.join(outDir, "mock_import_groups.csv"), groupCsv, "utf8"); console.log("グループ: ", groupOut.length, "件 (元1571行→集約)");