366 lines
16 KiB
JavaScript
366 lines
16 KiB
JavaScript
/**
|
||
* 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 SOURCE_SITE_ID = 480111;
|
||
const TARGET_SITE_ID = 496626;
|
||
|
||
const baseDir = path.join(__dirname, "..", "..");
|
||
const configsDir = path.join(baseDir, "configs");
|
||
|
||
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(" OK(17項目 × 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("営業タブに対応するView(Id:2, 営業担当項目)が見つかりません。");
|
||
const viewInsertIdx = salesView.GridColumns.indexOf("Date030") + 1;
|
||
salesView.GridColumns.splice(viewInsertIdx, 0, "Date031");
|
||
|
||
if (typeof settings.SectionLatestId === "number") {
|
||
settings.SectionLatestId = 49;
|
||
}
|
||
console.log(" OK(Columns 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(" OK(Check009/010/011を除外、Check018/ClassOを追加。列定義自体は保持)");
|
||
|
||
// ============================================================
|
||
// フェーズF: 物件詳細1(DescriptionA)入力ガイド追加
|
||
// ============================================================
|
||
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(` OK(Scripts ${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 で差分を確認してください。`);
|
||
}
|