ken_nogi/Pleasanter/.claude/js/site-scripts/新・着工要因システム/restructure-site-480111.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
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>
2026-09-04 15:37:06 +09:00

369 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* restructure-site-480111.js
* ------------------------------------------------------------
* configs/site-480111_.../sitesettings/site-480111_latest.json を読み込み、
* SiteId 496626 への適用データsite-480111_updated.jsonを生成する。
* 480111自体本番は一切変更しないこのスクリプトはファイルの読み書きのみ、APIへは送信しない
*
* 出力は SiteId 496626 の modify/{リクエストラベル}/ フォルダへ、496626の
* 変更前スナップショットbefore_site-496626_latest.jsonと併せて保存する
* 「修正依頼のたびに新しいフォルダを生成し、変更前後のconfigをまとめて保管する」規約
*
* 反映内容:
* フェーズB: 既存項目 Date031〜047系の連番を Date032〜048系へ1つずつ繰り下げ
* フェーズC: 新規項目「解体紹介料申請」を空いた031として挿入担当:営業)
* フェーズD: 予定/実行の標準GridDesign計38件の末尾に備考(Class)を追記
* フェーズE: 事前申請チェックリストの見直しCheck009/010/011除外、その他追加
* フェーズF: 物件詳細1(DescriptionA)への入力ガイド追加
* フェーズG: 事前に編集済みのScripts/Styles本文を反映
*
* 使い方:
* node restructure-site-480111.js … modifyフォルダを自動作成
* node restructure-site-480111.js --request=既存のラベル … 既存のmodifyフォルダに書き足す
* node restructure-site-480111.js [inputPath] [outputPath] … 手動でパスを指定modify規約を使わない
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const { findSiteDir, latestJsonPath, newModifyDir } = require("../../site-paths");
const { REPO_ROOT } = require("../../resolve-project");
const PROJECT_NAME = "新・着工要因システム";
const ENV = "production";
const SOURCE_SITE_ID = 480111;
const TARGET_SITE_ID = 496626;
const baseDir = path.join(REPO_ROOT, PROJECT_NAME);
const configsDir = path.join(baseDir, "configs", ENV);
const manualArgs = process.argv.slice(2).filter((a) => !a.startsWith("--"));
const requestArg = process.argv.find((a) => a.startsWith("--request="));
const requestLabel = requestArg ? requestArg.split("=")[1] : null;
const inputPath =
manualArgs[0] ||
latestJsonPath(configsDir, SOURCE_SITE_ID) ||
path.join(configsDir, `site-${SOURCE_SITE_ID}_latest.json`);
const siteDir = findSiteDir(configsDir, SOURCE_SITE_ID);
if (!siteDir) {
console.error(`[エラー] SiteId ${SOURCE_SITE_ID} のフォルダが見つかりません。先に node get-site-config.js を実行してください。`);
process.exit(1);
}
let outputPath = manualArgs[1];
let modifyDir = null;
if (!outputPath) {
const targetSiteDir = findSiteDir(configsDir, TARGET_SITE_ID);
if (!targetSiteDir) {
console.error(`[エラー] SiteId ${TARGET_SITE_ID} のフォルダが見つかりません。先に node get-site-config.js を実行してください。`);
process.exit(1);
}
modifyDir = newModifyDir(targetSiteDir, requestLabel);
outputPath = path.join(modifyDir, "site-480111_updated.json");
// 496626の変更前スナップショットも同じフォルダに保管する
const beforePath = latestJsonPath(configsDir, TARGET_SITE_ID);
if (beforePath && fs.existsSync(beforePath)) {
fs.copyFileSync(beforePath, path.join(modifyDir, `before_site-${TARGET_SITE_ID}_latest.json`));
}
}
function fail(msg) {
console.error(`[エラー] ${msg}`);
process.exit(1);
}
function pad3(n) {
return String(n).padStart(3, "0");
}
if (!fs.existsSync(inputPath)) {
fail(`入力ファイルが見つかりません: ${inputPath}\n先に node get-site-config.js を実行してください。`);
}
const json = JSON.parse(fs.readFileSync(inputPath, "utf-8"));
const settings = json?.Response?.Data?.SiteSettings;
if (!settings) fail("Response.Data.SiteSettings が見つかりません。");
const columns = settings.Columns;
const editorColumnHash = settings.EditorColumnHash || {};
const sections = settings.Sections;
const views = settings.Views || [];
if (!Array.isArray(columns) || !Array.isArray(sections) || !Array.isArray(settings.GridColumns)) {
fail("Columns / Sections / GridColumns の構造が想定と異なります。");
}
const originalCounts = {
columns: columns.length,
sections: sections.length,
gridColumns: settings.GridColumns.length,
};
function columnExists(name) {
return columns.some((c) => c.ColumnName === name);
}
// ============================================================
// フェーズA: 事前ガード
// ============================================================
console.log("[フェーズA] 事前ガードを確認しています...");
if (
columnExists("Date048") ||
columnExists("Class048") ||
columnExists("Check148") ||
columnExists("Check018") ||
columnExists("ClassO")
) {
fail(
"Date048/Class048/Check148/Check018/ClassO のいずれかが既に存在します。" +
"本スクリプトを二重実行していないか確認してください。"
);
}
if ((editorColumnHash["_Tab-1"] || []).includes("_Section-48")) {
fail("_Section-48 が既に EditorColumnHash._Tab-1 に存在します。二重実行の可能性があります。");
}
const class030Idx = columns.findIndex((c) => c.ColumnName === "Class030");
if (class030Idx === -1 || columns[class030Idx + 1]?.ColumnName !== "Date031") {
fail("Class030 の直後が Date031 ではありません。想定と異なる構造のため中断します。");
}
console.log(" OK");
// ============================================================
// フェーズB: 連番リネーム(降順 47→31、衝突回避
// ============================================================
console.log("[フェーズB] 連番リネームを実行しています047→048 … 031→032...");
for (let i = 47; i >= 31; i--) {
const oldN = pad3(i);
const newN = pad3(i + 1);
const old100 = String(i + 100);
const new100 = String(i + 101);
const map = {
["Date" + oldN]: "Date" + newN,
["Date" + old100]: "Date" + new100,
["Check" + old100]: "Check" + new100,
["Class" + oldN]: "Class" + newN,
["_Section-" + i]: "_Section-" + (i + 1),
};
// Columns[]: ColumnName本体 + 自己参照するGridDesign内のブラケットトークン
for (const col of columns) {
if (map[col.ColumnName]) col.ColumnName = map[col.ColumnName];
if (col.GridDesign) {
for (const [oldTok, newTok] of Object.entries(map)) {
col.GridDesign = col.GridDesign.split(`[${oldTok}]`).join(`[${newTok}]`);
}
}
}
// EditorColumnHash: 全キーGeneral, _Tab-1〜_Tab-5を走査
for (const key of Object.keys(editorColumnHash)) {
editorColumnHash[key] = editorColumnHash[key].map((tok) => map[tok] ?? tok);
}
// Sections[].Id
for (const sec of sections) {
if (sec.Id === i) sec.Id = i + 1;
}
// トップレベル GridColumns[]
settings.GridColumns = settings.GridColumns.map((tok) => map[tok] ?? tok);
// Views[]各要素の GridColumns[](存在する場合のみ)
for (const view of views) {
if (Array.isArray(view.GridColumns)) {
view.GridColumns = view.GridColumns.map((tok) => map[tok] ?? tok);
}
}
}
console.log(" OK17項目 × 4列 + セクション を繰り下げ)");
// ============================================================
// フェーズC: 新規項目「解体紹介料申請」をsuffix 031として挿入
// ============================================================
console.log("[フェーズC] 新規項目「解体紹介料申請」を挿入しています...");
const newQuad = [
{
ColumnName: "Date031",
LabelText: "予定",
GridLabelText: "解体紹介料申請",
Description: "営業",
GridFormat: "Md",
NoWrap: true,
GridDesign: "[Date031]\n\n-----\n[Date131]", // フェーズDで標準パターンへ書き換えられる
},
{ ColumnName: "Date131", LabelText: "実行", GridFormat: "Md" },
{ ColumnName: "Check131", LabelText: "無し" },
{ ColumnName: "Class031", LabelText: "備考", FieldCss: "field-wide" },
];
const insertColIdx = columns.findIndex((c) => c.ColumnName === "Class030") + 1;
columns.splice(insertColIdx, 0, ...newQuad);
const tab1 = editorColumnHash["_Tab-1"];
if (!tab1) fail("EditorColumnHash._Tab-1 が見つかりません。");
const tab1InsertIdx = tab1.indexOf("Class030") + 1;
tab1.splice(tab1InsertIdx, 0, "_Section-31", "Date031", "Date131", "Check131", "Class031");
const sectionInsertIdx = sections.findIndex((s) => s.Id === 30) + 1;
sections.splice(sectionInsertIdx, 0, {
Id: 31,
LabelText: "解体紹介料申請",
AllowExpand: false,
Expand: true,
});
const gcInsertIdx = settings.GridColumns.indexOf("Date030") + 1;
settings.GridColumns.splice(gcInsertIdx, 0, "Date031");
const salesView = views.find((v) => v.Id === 2 && Array.isArray(v.GridColumns));
if (!salesView) fail("営業タブに対応するViewId:2, 営業担当項目)が見つかりません。");
const viewInsertIdx = salesView.GridColumns.indexOf("Date030") + 1;
salesView.GridColumns.splice(viewInsertIdx, 0, "Date031");
if (typeof settings.SectionLatestId === "number") {
settings.SectionLatestId = 49;
}
console.log(" OKColumns 4列 / Section / GridColumns / View を追加)");
// ============================================================
// フェーズD: GridDesign一括書き換え標準の予定/実行ペアのみ)
// ============================================================
console.log("[フェーズD] GridDesignを一括書き換えしています...");
const STANDARD_RE = /^\[(Date\d{3})\]\n\n-----\n\[(Date\d{3})\]$/;
let gridDesignRewritten = 0;
for (const col of columns) {
if (!col.GridDesign) continue;
const m = col.GridDesign.match(STANDARD_RE);
if (!m) continue;
const [, dateTok, date100Tok] = m;
const suffix = dateTok.slice(4);
col.GridDesign = `[${dateTok}]\n-----\n[${date100Tok}]\n-----\n[Class${suffix}]`;
gridDesignRewritten++;
}
console.log(` OK${gridDesignRewritten}件を書き換え)`);
// ============================================================
// フェーズE: 事前申請チェックリストの見直し
// ============================================================
console.log("[フェーズE] 事前申請チェックリストを見直しています...");
const general = editorColumnHash["General"];
if (!general) fail("EditorColumnHash.General が見つかりません。");
for (const tok of ["Check009", "Check010", "Check011"]) {
const idx = general.indexOf(tok);
if (idx !== -1) general.splice(idx, 1);
}
const check017Idx = general.indexOf("Check017");
if (check017Idx === -1) fail("EditorColumnHash.General に Check017 が見つかりません。");
general.splice(check017Idx + 1, 0, "Check018", "ClassO");
const check017ColIdx = columns.findIndex((c) => c.ColumnName === "Check017");
if (check017ColIdx === -1) fail("Columns に Check017 が見つかりません。");
columns.splice(check017ColIdx + 1, 0,
{ ColumnName: "Check018", LabelText: "その他" },
{
ColumnName: "ClassO",
LabelText: "その他内容",
FieldCss: "field-wide",
Description: "上記に該当しない事前申請がある場合に入力してください",
}
);
console.log(" OKCheck009/010/011を除外、Check018/ClassOを追加。列定義自体は保持");
// ============================================================
// フェーズF: 物件詳細1DescriptionA入力ガイド追加
// ============================================================
console.log("[フェーズF] DescriptionAへ入力ガイドを追加しています...");
const descA = columns.find((c) => c.ColumnName === "DescriptionA");
if (!descA) fail("Columns に DescriptionA が見つかりません。");
descA.Description = "手入力してください";
console.log(" OK");
// ============================================================
// フェーズG: Scripts/Stylesの反映事前に編集済みの抽出ファイルを読み込む
// ============================================================
console.log("[フェーズG] 編集済みのScripts/Stylesを反映しています...");
const manifestPath = path.join(siteDir, "manifest.json");
if (!fs.existsSync(manifestPath)) fail(`manifest.jsonが見つかりません: ${manifestPath}`);
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
function applyBodies(list, manifestEntries, dir, label) {
let count = 0;
for (const entry of manifestEntries || []) {
const target = (list || []).find((x) => x.Id === entry.Id);
if (!target) continue;
const filePath = path.join(dir, entry.File);
if (!fs.existsSync(filePath)) fail(`${label}のファイルが見つかりません: ${filePath}`);
target.Body = fs.readFileSync(filePath, "utf-8");
count++;
}
return count;
}
const scriptsUpdated = applyBodies(settings.Scripts, manifest.Scripts, path.join(siteDir, "scripts"), "Scripts");
const stylesUpdated = applyBodies(settings.Styles, manifest.Styles, path.join(siteDir, "styles"), "Styles");
console.log(` OKScripts ${scriptsUpdated}件 / Styles ${stylesUpdated}件を反映)`);
// ============================================================
// フェーズH: 書き込み前の整合性チェック
// ============================================================
console.log("[フェーズH] 整合性チェックを実行しています...");
if (columns.length !== originalCounts.columns + 6) {
fail(`Columns件数が想定と異なります期待: +6, 実際: +${columns.length - originalCounts.columns}`);
}
if (sections.length !== originalCounts.sections + 1) {
fail(`Sections件数が想定と異なります期待: +1, 実際: +${sections.length - originalCounts.sections}`);
}
if (settings.GridColumns.length !== originalCounts.gridColumns + 1) {
fail(`GridColumns件数が想定と異なります期待: +1, 実際: +${settings.GridColumns.length - originalCounts.gridColumns}`);
}
const colNames = columns.map((c) => c.ColumnName);
if (new Set(colNames).size !== colNames.length) fail("ColumnNameに重複があります。");
const sectionIds = sections.map((s) => s.Id);
if (new Set(sectionIds).size !== sectionIds.length) fail("Section.Idに重複があります。");
for (const key of Object.keys(editorColumnHash)) {
const arr = editorColumnHash[key];
if (new Set(arr).size !== arr.length) fail(`EditorColumnHash.${key} 内にトークンの重複があります。`);
}
const STANDARD_FINAL_RE = /^\[Date\d{3}\]\n-----\n\[Date\d{3}\]\n-----\n\[Class\d{3}\]$/;
const finalCount = columns.filter((c) => c.GridDesign && STANDARD_FINAL_RE.test(c.GridDesign)).length;
if (finalCount !== 38) {
fail(`GridDesignの新パターン一致件数が38件ではありません実際: ${finalCount}件)`);
}
console.log(" OK全チェック通過");
// ============================================================
// 書き出し
// ============================================================
fs.writeFileSync(outputPath, JSON.stringify(json, null, 2), "utf-8");
console.log("\n========================================");
console.log(" 完了");
console.log("========================================");
console.log(`出力先: ${outputPath}`);
console.log(` Columns : ${originalCounts.columns}${columns.length}`);
console.log(` Sections : ${originalCounts.sections}${sections.length}`);
console.log(` GridColumns : ${originalCounts.gridColumns}${settings.GridColumns.length}`);
console.log(` GridDesign書き換え: ${gridDesignRewritten}`);
if (modifyDir) {
console.log(`\nmodifyフォルダ: ${modifyDir}`);
console.log(`次のステップ: node apply-to-site-496626.js --request=${path.basename(modifyDir)} で差分を確認してください。`);
} else {
console.log(`\n次のステップ: node apply-to-site-496626.js で差分を確認してください。`);
}