GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
221 lines
9.6 KiB
JavaScript
221 lines
9.6 KiB
JavaScript
/**
|
||
* 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);
|
||
})();
|