/** * apply-9item-phase2-496626.js * ------------------------------------------------------------ * site-496626「新・着工要因(東京)v2」9項目改修のitem8 * (「引越し」工程削除+後続20工程の内部カラム番号一括リネーム)の * SiteSettings構造変更(Sections/Columns/EditorColumnHash/GridColumns)を反映する。 * * 対象: Date028(引越し)を削除し、Date029〜048(20工程)を Date028〜047 へ、 * 対応する実績列(Date1xx)・備考列(Class0xx)・無しチェック列(Check1xx)・Section Idも * 全て1つずつ前へリネームする。工程名(LabelText/GridLabelText)自体は変更しない。 * * ★このスクリプトはSiteSettings構造のみを変更する。既存レコードのデータ * (Date029等の実際の値)はこの反映だけでは移行されない。データ移行は * migrate-process028-rename-496626.js を別途実行すること。構造変更を先に * 反映し、その直後にデータ移行を実行する運用を想定している。 * * 使い方: * node apply-9item-phase2-496626.js … 差分表示のみ(送信なし) * node apply-9item-phase2-496626.js --execute … 上記に加えて実際に送信する * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const { findSiteDir, newModifyDir, latestModifyDir } = require("../../site-paths"); const { REPO_ROOT, loadServerConfig } = require("../../resolve-project"); const PROJECT_NAME = "新・着工要因システム"; const ENV = "production"; const SITE_ID = 496626; const baseDir = path.join(REPO_ROOT, PROJECT_NAME); const configsDir = path.join(baseDir, "configs", ENV); const execute = process.argv.includes("--execute"); function loadJson(p) { return JSON.parse(fs.readFileSync(p, "utf-8")); } function maskApiKey(key) { if (!key) return key; return key.length > 8 ? key.slice(0, 4) + "****" + key.slice(-4) : "****"; } function timestamp() { return new Date().toISOString().replace(/[:.]/g, "-"); } const config = loadServerConfig(ENV); const siteDir = findSiteDir(configsDir, SITE_ID); // ---- リネームマッピング構築 ---- const REMOVE_COLUMNS = new Set(["Date028", "Date128", "Class028", "Check128"]); const RENAME_MAP = {}; const SECTION_ID_MAP = {}; for (let n = 29; n <= 48; n++) { const newN = n - 1; const pad = (x) => String(x).padStart(3, "0"); RENAME_MAP["Date" + pad(n)] = "Date" + pad(newN); RENAME_MAP["Date" + (n + 100)] = "Date" + (newN + 100); RENAME_MAP["Class" + pad(n)] = "Class" + pad(newN); RENAME_MAP["Check" + (n + 100)] = "Check" + (newN + 100); SECTION_ID_MAP[n] = newN; } function renameGridDesign(text) { if (!text) return text; return text.replace(/\[([A-Za-z]+\d+)\]/g, (m, colName) => { return RENAME_MAP[colName] ? `[${RENAME_MAP[colName]}]` : m; }); } function renameTokenArray(arr) { return arr .filter((t) => t !== "_Section-28" && !REMOVE_COLUMNS.has(t)) .map((t) => { if (t.startsWith("_Section-")) { const id = parseInt(t.replace("_Section-", ""), 10); return SECTION_ID_MAP[id] ? `_Section-${SECTION_ID_MAP[id]}` : t; } return RENAME_MAP[t] || t; }); } function transform(ss) { const log = []; // ---- Columns ---- ss.Columns = ss.Columns.filter((c) => !REMOVE_COLUMNS.has(c.ColumnName)); ss.Columns.forEach((c) => { if (RENAME_MAP[c.ColumnName]) { c.ColumnName = RENAME_MAP[c.ColumnName]; } if (c.GridDesign) { c.GridDesign = renameGridDesign(c.GridDesign); } }); log.push("Columns: Date028系4項目削除、Date029〜048系80項目をリネーム(GridDesign内参照含む)"); // ---- Sections ---- ss.Sections = ss.Sections.filter((s) => s.Id !== 28); ss.Sections.forEach((s) => { if (SECTION_ID_MAP[s.Id]) { s.Id = SECTION_ID_MAP[s.Id]; } }); log.push("Sections: Id28削除、Id29〜48をId28〜47へリネーム(LabelTextは変更なし)"); // ---- EditorColumnHash(5タブ全て) ---- ["_Tab-1", "_Tab-2", "_Tab-3", "_Tab-4", "_Tab-5"].forEach((tab) => { if (ss.EditorColumnHash[tab]) { const before = ss.EditorColumnHash[tab].length; ss.EditorColumnHash[tab] = renameTokenArray(ss.EditorColumnHash[tab]); log.push(`EditorColumnHash.${tab}: ${before}トークン -> ${ss.EditorColumnHash[tab].length}トークン`); } }); // ---- GridColumns(メイン + View2/5/6) ---- ss.GridColumns = renameTokenArray(ss.GridColumns); ss.Views.forEach((v) => { if (v.GridColumns) { v.GridColumns = renameTokenArray(v.GridColumns); } }); log.push("GridColumns: メイン・View2・View5・View6の4箇所をリネーム"); return log; } (async () => { console.log("[INFO] 現在のサイト情報を取得しています..."); const getUrl = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${SITE_ID}/getsite`; const getRes = await fetch(getUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ApiVersion: config.ApiVersion || "1.1", ApiKey: config.ApiKey }), }); const getJson = await getRes.json(); if (getJson.StatusCode !== 200) { console.error("[エラー] getsiteに失敗しました:", JSON.stringify(getJson).slice(0, 500)); process.exit(1); } const data = getJson.Response.Data; console.log(`[OK] 取得しました(現在のVer: ${data.Ver}, UpdatedTime: ${data.UpdatedTime})`); const siteSettings = JSON.parse(JSON.stringify(data.SiteSettings)); console.log("========================================"); console.log(` 9項目改修 Phase2(item8)引越し削除+20工程番号リネーム(updatesite全体更新)${execute ? "(実行)" : "(プレビューのみ)"}`); console.log("========================================"); const log = transform(siteSettings); log.forEach((l) => console.log(" " + l)); const body = { ApiVersion: config.ApiVersion || "1.1", ApiKey: config.ApiKey, SiteId: SITE_ID, Title: data.Title, ReferenceType: data.ReferenceType, ParentId: data.ParentId, InheritPermission: data.InheritPermission, Permissions: data.Permissions, SiteSettings: siteSettings, }; const modifyDir = latestModifyDir(siteDir) || newModifyDir(siteDir, "9item-revision"); const ts = timestamp(); const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) }; const previewPath = path.join(modifyDir, `site-${SITE_ID}_phase2_update_preview_${ts}.json`); fs.writeFileSync(previewPath, JSON.stringify(maskedBody, null, 2), "utf-8"); console.log(`\n[OK] 送信予定内容を保存しました: ${previewPath}`); if (!execute) { console.log("\n[注意] --execute が指定されていないため、送信は行っていません。"); console.log("内容を確認の上、問題なければ次を実行してください: node apply-9item-phase2-496626.js --execute"); process.exit(0); } console.log("\n実際に送信します(updatesite)..."); const url = `${config.BaseUrl.replace(/\/+$/, "")}/api/items/${SITE_ID}/updatesite`; const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const text = await res.text(); const resultOutPath = path.join(modifyDir, `site-${SITE_ID}_phase2_update_result_${ts}.json`); fs.writeFileSync(resultOutPath, text, "utf-8"); console.log(`HTTP ${res.status} ${res.statusText}`); console.log(`[OK] 応答を保存しました: ${resultOutPath}`); console.log(text); })();