/** * restructure-site-335411.js * ------------------------------------------------------------ * site-335411(②問い合わせ内容管理)へ以下をまとめて反映する(Task2、フル更新)。 * Scripts/Styles/ServerScripts/Processesは一切変更しない(現状のまま送信)。 * * A. StatusControls Id:1「依頼ロック」ColumnHashへ DescriptionC:"ReadOnly" 追加 * (要望①: 積算へ依頼後、依頼内容編集不可化) * B. Sections LabelText変更 Id:9/14(営業積算課→営業積算、要望④の画面表示分) * C. 新規Columns4点追加(要望②a/b/c, ⑦) * - Class088 担当監督(SiteId 210712参照、単純選択) * - Check073 稟議ではないことを確認済み * - Check074 承認図後追加変更契約 定義書①~④以外である * - Class089 不要理由(SiteId 335603参照、Lookups: DescriptionA→Description091、AutoPostBack) * EditorColumnHash.General: DescriptionA(問い合わせ経緯)の直後にClass088/Check073/Check074を挿入 * EditorColumnHash._Tab-2: Description091(見積回答)の直前にClass089を挿入 * D. Links配列へ Class088→210712, Class089→335603 を追加 * E. 既存Columns8項目(Date081/Class081, Date082/Class082, Date103/Class103, Date084/Class087。 * プロセス自動設定項目)へ ExtendedFieldCss:"code-readonly2" 追加(要望⑥) * * 使い方: * node restructure-site-335411.js … 差分表示のみ(送信なし) * node restructure-site-335411.js --execute … 実際に送信する * ------------------------------------------------------------ */ const fs = require("fs"); const path = require("path"); const { findSiteDir, newModifyDir } = require("../../site-paths"); const { REPO_ROOT, loadServerConfig } = require("../../resolve-project"); const PROJECT_NAME = "営業積算システム"; const ENV = "development"; // このサイトはテストサーバー(neo999.next-hd.net)専用 const baseDir = path.join(REPO_ROOT, PROJECT_NAME); const execute = process.argv.includes("--execute"); const SITE_ID = 335411; 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, "-"); } function insertAfter(arr, afterName, items) { const idx = arr.indexOf(afterName); if (idx === -1) throw new Error(`挿入基準列が見つかりません: ${afterName}`); arr.splice(idx + 1, 0, ...items); } function insertBefore(arr, beforeName, items) { const idx = arr.indexOf(beforeName); if (idx === -1) throw new Error(`挿入基準列が見つかりません: ${beforeName}`); arr.splice(idx, 0, ...items); } const config = loadServerConfig(ENV); const siteDir = findSiteDir(path.join(baseDir, "configs", ENV), SITE_ID); (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 ss = JSON.parse(JSON.stringify(data.SiteSettings)); // --- A. 依頼ロックへDescriptionC追加 --- const lockControl = (ss.StatusControls || []).find((s) => s.Id === 1); if (!lockControl) throw new Error("StatusControls Id:1(依頼ロック)が見つかりません"); if (lockControl.ColumnHash.DescriptionC) { console.log("[SKIP] DescriptionCは既に依頼ロック対象です"); } else { lockControl.ColumnHash.DescriptionC = "ReadOnly"; console.log("[A] StatusControls Id:1 ColumnHashへ DescriptionC:ReadOnly を追加"); } // --- B. Sections LabelText変更 --- const sec9 = (ss.Sections || []).find((s) => s.Id === 9); const sec14 = (ss.Sections || []).find((s) => s.Id === 14); if (!sec9 || !sec14) throw new Error("Sections Id:9またはId:14が見つかりません"); console.log(`[B] Sections Id:9 "${sec9.LabelText}" -> "営業積算からの質疑"`); console.log(`[B] Sections Id:14 "${sec14.LabelText}" -> "営業積算からの引継ぎ"`); sec9.LabelText = "営業積算からの質疑"; sec14.LabelText = "営業積算からの引継ぎ"; // --- C. 新規Columns4点 --- const newColumns = [ { ColumnName: "Class088", LabelText: "担当監督", ChoicesText: '[\n {\n "SiteId": 210712\n }\n]', UseSearch: true, SearchType: "PartialMatch", }, { ColumnName: "Check073", LabelText: "稟議ではないことを確認済み", NoWrap: true, }, { ColumnName: "Check074", LabelText: "承認図後追加変更契約 定義書①~④以外である", NoWrap: true, }, { ColumnName: "Class089", LabelText: "不要理由", ChoicesText: '[\n {\n "SiteId": 335603,\n "View": {\n "ColumnSorterHash": {\n "NumA": "asc"\n }\n },\n "Lookups": [\n {\n "From": "DescriptionA",\n "To": "Description091"\n }\n ]\n }\n]', AutoPostBack: true, ColumnsReturnedWhenAutomaticPostback: "Description091", SearchType: "PartialMatch", }, ]; const existingNames = new Set((ss.Columns || []).map((c) => c.ColumnName)); newColumns.forEach((c) => { if (existingNames.has(c.ColumnName)) throw new Error(`ColumnName重複: ${c.ColumnName}`); }); ss.Columns.push(...newColumns); console.log(`[C] Columns新規追加: ${newColumns.map((c) => c.ColumnName + "(" + c.LabelText + ")").join(", ")}`); // --- EditorColumnHash挿入 --- insertAfter(ss.EditorColumnHash.General, "DescriptionA", ["Class088", "Check073", "Check074"]); console.log('[C] EditorColumnHash.General: DescriptionAの直後にClass088/Check073/Check074を挿入'); insertBefore(ss.EditorColumnHash["_Tab-2"], "Description091", ["Class089"]); console.log('[C] EditorColumnHash._Tab-2: Description091の直前にClass089を挿入'); // --- D. Links追加 --- ss.Links = ss.Links || []; ss.Links.push({ ColumnName: "Class088", SiteId: 210712 }); ss.Links.push({ ColumnName: "Class089", SiteId: 335603, View: { Id: 0, ColumnSorterHash: { NumA: "asc" }, ApiColumnKeyDisplayType: 0, ApiColumnValueDisplayType: 0, CalendarSiteId: 0, ApiDataType: 0, }, Lookups: [{ From: "DescriptionA", To: "Description091" }], JsonFormat: true, }); console.log(`[D] Links追加後の件数: ${ss.Links.length}(Class088→210712, Class089→335603を追加)`); // --- E. 読み取り専用化(プロセス自動設定項目8列) --- const readonlyTargets = ["Date081", "Class081", "Date082", "Class082", "Date103", "Class103", "Date084", "Class087"]; readonlyTargets.forEach((name) => { const col = (ss.Columns || []).find((c) => c.ColumnName === name); if (!col) throw new Error(`読取専用化対象列が見つかりません: ${name}`); if (col.ExtendedFieldCss === "code-readonly2") { console.log(`[SKIP][E] ${name}(${col.LabelText})は既にcode-readonly2設定済み`); return; } col.ExtendedFieldCss = "code-readonly2"; console.log(`[E] ${name}(${col.LabelText})に ExtendedFieldCss:"code-readonly2" を追加`); }); 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: ss, }; const modifyDir = newModifyDir(siteDir, `2026-08-11T${new Date().toTimeString().slice(0, 5).replace(":", "")}_要望9件対応フル更新`); const ts = timestamp(); const maskedBody = { ...body, ApiKey: maskApiKey(body.ApiKey) }; const previewPath = path.join(modifyDir, `site-${SITE_ID}_full_update_preview_${ts}.json`); fs.writeFileSync(previewPath, JSON.stringify(maskedBody, null, 2), "utf-8"); console.log(`\n[OK] 送信予定内容を保存しました: ${previewPath}`); const beforePath = path.join(modifyDir, `before_site-${SITE_ID}_latest.json`); fs.writeFileSync(beforePath, JSON.stringify(getJson, null, 2), "utf-8"); console.log(`[OK] 変更前スナップショットを保存しました: ${beforePath}`); if (!execute) { console.log("\n[注意] --execute が指定されていないため、送信は行っていません。"); console.log("内容を確認の上、問題なければ次を実行してください: node restructure-site-335411.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}_full_update_result_${ts}.json`); fs.writeFileSync(resultOutPath, text, "utf-8"); console.log(`HTTP ${res.status} ${res.statusText}`); console.log(`[OK] 応答を保存しました: ${resultOutPath}`); console.log(text); })();