ken_nogi/Pleasanter/.claude/js/site-scripts/NEXTGROUP経営計画/batch-refresh-507570.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

572 lines
23 KiB
JavaScript
Raw 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.

/**
* batch-refresh-507570.js
* ------------------------------------------------------------
* SiteId 507570事業計画v2専用のワンオフスクリプト。
* ブラウザで手動実施している「1) レコードを開く→実績計上(263191)読込→
* 2) 保存→集計テーブル(507676)へ反映」を、scripts/1_事業計画テーブル.jsの
* getJissekiData()・summaryCalc()・summaryRecord()/bulkUpsertRecords()と
* 同一ロジックでAPI経由で再現し、対象レコードを一括処理する。
*
* 処理内容1レコードあたり:
* Phase A: 263191から受注(本契約/設計契約)・着工・完成を取得し、
* 月別にDescription021-032(受注実績)/081-092(着工実績)/141-152(完成実績)へ格納
* ※ getJissekiData()と同一ロジック年度範囲10/1-9/30、月境界チェック等
* Phase B: 既存の計画(Description001-012等)+Phase Aの実績から、
* 受注/着工/完成それぞれの月別合計・累計をsummaryCalc()と同一ロジックで計算し
* Description041-052/101-112/161-172(合計)へ格納
* Phase C: Phase Bの合計(12ヶ月×3系統=36件)をsummaryRecord()と同一ロジックで
* ClassHash/NumHashに組み立て、507676へbulkupsertで1回送信
* Phase D: Phase A+Bの結果を507570へ1回のupdateでまとめて保存
*
* 使い方:
* node batch-refresh-507570.js … 全レコード対象、プレビューのみ(送信なし)
* node batch-refresh-507570.js --only=507580 … 指定ResultIdのみ対象テスト用
* node batch-refresh-507570.js --execute … 実際に507570 update + 507676 bulkupsert を送信
* --only と --execute は併用可
* ------------------------------------------------------------
*/
const fs = require("fs");
const path = require("path");
const https = require("https");
const PROJECT_NAME = "NEXTGROUP経営計画";
const ENV = "production";
const SITE_ID = 507570;
const JISSEKI_SITE_ID = 263191;
const SHUKEI_SITE_ID = 507676;
const REPO_ROOT = path.join(__dirname, "..", "..", "..", "..");
const baseDir = path.join(REPO_ROOT, PROJECT_NAME);
const siteDir = path.join(baseDir, "configs", ENV, `site-${SITE_ID}_事業計画v2`);
const modifyDir = path.join(siteDir, "modify", "batch-refresh");
const configPath = path.join(REPO_ROOT, `config_${ENV}.json`);
const serverConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
const apiKey = serverConfig.ApiKey;
const baseUrl = serverConfig.BaseUrl;
const argv = process.argv.slice(2);
const execute = argv.includes("--execute");
const onlyArg = argv.find((a) => a.startsWith("--only="));
const onlyId = onlyArg ? parseInt(onlyArg.split("=")[1], 10) : null;
function post(pathName, body) {
return new Promise((resolve, reject) => {
const url = new URL(baseUrl + pathName);
const payload = JSON.stringify(body);
const req = https.request(
{
hostname: url.hostname,
path: url.pathname,
method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) },
},
(res) => {
let c = "";
res.on("data", (d) => (c += d));
res.on("end", () => {
try {
resolve({ status: res.statusCode, body: JSON.parse(c) });
} catch (e) {
resolve({ status: res.statusCode, body: c });
}
});
}
);
req.on("error", reject);
req.write(payload);
req.end();
});
}
function pad3(n) {
return String(n).padStart(3, "0");
}
// getJissekiData()の空行テンプレート(1-10)
function emptyJissekiTemplate() {
const rows = [];
for (let k = 1; k <= 10; k++) {
rows.push({ 0: String(k), 1: "", 2: "", 3: "", 4: "", 5: "", 6: "", 7: "" });
}
return rows;
}
// jissekiA/B/C[m]配列(最大10件超は元コードも考慮していないため同様に踏襲)を
// 10行テンプレートへ整形するgetJissekiData()の394-462行と同一ロジック
function buildJissekiRows(rows) {
if (!rows.length) return emptyJissekiTemplate();
const out = rows.map((r, idx) => ({ ...r, 0: String(idx + 1) }));
for (let k = 0; k < 10; k++) {
if (k >= out.length) {
out[k] = { 0: String(k + 1), 1: "", 2: "", 3: "", 4: "", 5: "", 6: "", 7: "" };
}
}
return out;
}
// SiteSettings.Columnsを見ずに済むよう、getJigyouKeikakuData同様Description番号直指定で読み書きする
async function fetchJisseki(classE, sYear, eYear, sDate, eDate, eDate1) {
const jissekiA = {}, jissekiB = {}, jissekiC = {};
for (let m = 1; m <= 12; m++) {
jissekiA[m] = [];
jissekiB[m] = [];
jissekiC[m] = [];
}
// 受注(本契約のみ)
{
const res = await post(`/api/items/${JISSEKI_SITE_ID}/get`, {
ApiVersion: 1.1,
ApiKey: apiKey,
View: {
ColumnFilterHash: {
ClassE: `["${classE}"]`,
ClassO: `["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"]`,
DateA: `["${sDate},${eDate}"]`,
DateB: `["\t"]`,
},
ColumnSorterHash: { DateA: "asc" },
},
});
for (const value of res.body.Response.Data) {
const countMonth = parseFloat(value.ClassHash.ClassO.replace("月", ""));
const date0 = new Date(value.DateHash.DateA);
date0.setHours(date0.getHours() + 9);
const countYear = date0.getFullYear();
if (countMonth <= 9 && countYear == sYear) continue;
if (countMonth == 10 && countYear == eYear) continue;
const kubun = value.ClassHash.ClassD;
const cname = value.ClassHash.ClassC;
const ukeoi = parseFloat(value.NumHash.NumA);
const arari = parseFloat(value.NumHash.NumB);
const arariPer = Math.floor((arari / ukeoi) * 1000) / 10;
const tanto = value.ClassHash.ClassF;
jissekiA[countMonth].push({ 0: "0", 1: date0, 2: kubun, 3: cname, 4: ukeoi, 5: arariPer, 6: arari, 7: tanto });
}
}
// 受注(設計契約)
{
const res = await post(`/api/items/${JISSEKI_SITE_ID}/get`, {
ApiVersion: 1.1,
ApiKey: apiKey,
View: {
ColumnFilterHash: {
ClassE: `["${classE}"]`,
ClassO: `["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"]`,
DateB: `["${sDate},${eDate}"]`,
},
ColumnSorterHash: { DateB: "asc" },
},
});
for (const value of res.body.Response.Data) {
const countMonth = parseFloat(value.ClassHash.ClassO.replace("月", ""));
const date0 = new Date(value.DateHash.DateB);
date0.setHours(date0.getHours() + 9);
const countYear = date0.getFullYear();
if (countMonth <= 9 && countYear == sYear) continue;
if (countMonth == 10 && countYear == eYear) continue;
const kubun = value.ClassHash.ClassD;
let cname, ukeoi, arari;
if (value.DateHash.DateA != "1899-12-30T00:00:00") {
cname = "[本]" + value.ClassHash.ClassC;
ukeoi = parseFloat(value.NumHash.NumA);
arari = parseFloat(value.NumHash.NumB);
} else {
cname = "[設]" + value.ClassHash.ClassC;
ukeoi = parseFloat(value.NumHash.NumC);
arari = parseFloat(value.NumHash.NumD);
}
const tanto = value.ClassHash.ClassF;
const arariPer = Math.floor((arari / ukeoi) * 1000) / 10;
jissekiA[countMonth].push({ 0: "0", 1: date0, 2: kubun, 3: cname, 4: ukeoi, 5: arariPer, 6: arari, 7: tanto });
}
}
// 着工
{
const res = await post(`/api/items/${JISSEKI_SITE_ID}/get`, {
ApiVersion: 1.1,
ApiKey: apiKey,
View: {
ColumnFilterHash: { ClassE: `["${classE}"]`, DateC: `["${sDate},${eDate1}"]` },
ColumnSorterHash: { DateC: "asc" },
},
});
let num = 1, month0 = 0;
res.body.Response.Data.forEach((value, index) => {
const date0 = new Date(value.DateHash.DateC);
date0.setHours(date0.getHours() + 9);
const month = date0.getMonth() + 1;
const kubun = value.ClassHash.ClassD;
const cname = value.ClassHash.ClassC;
const ukeoi = parseFloat(value.NumHash.NumA);
const arari = parseFloat(value.NumHash.NumB);
const arariPer = Math.floor((arari / ukeoi) * 1000) / 10;
const tanto = value.ClassHash.ClassF;
if (index > 0 && month0 != month) num = 1;
jissekiB[month].push({ 0: num, 1: date0, 2: kubun, 3: cname, 4: ukeoi, 5: arariPer, 6: arari, 7: tanto });
num++;
month0 = month;
});
}
// 完成
{
const res = await post(`/api/items/${JISSEKI_SITE_ID}/get`, {
ApiVersion: 1.1,
ApiKey: apiKey,
View: {
ColumnFilterHash: { ClassE: `["${classE}"]`, DateD: `["${sDate},${eDate1}"]` },
ColumnSorterHash: { DateD: "asc" },
},
});
let num = 1, month0 = 0;
res.body.Response.Data.forEach((value, index) => {
const date0 = new Date(value.DateHash.DateD);
date0.setHours(date0.getHours() + 9);
const month = date0.getMonth() + 1;
const kubun = value.ClassHash.ClassD;
const cname = value.ClassHash.ClassC;
const ukeoi = parseFloat(value.NumHash.NumA);
const arari = parseFloat(value.NumHash.NumB);
const arariPer = Math.floor((arari / ukeoi) * 1000) / 10;
const tanto = value.ClassHash.ClassF;
if (index > 0 && month0 != month) num = 1;
jissekiC[month].push({ 0: num, 1: date0, 2: kubun, 3: cname, 4: ukeoi, 5: arariPer, 6: arari, 7: tanto });
num++;
month0 = month;
});
}
return { jissekiA, jissekiB, jissekiC };
}
// summaryCalc()と同一ロジック。DescriptionHash(既存の計画+今回の実績)からgoukeiDataを計算する
function calcGoukei(descriptionHash) {
const keikakuCumulative = {};
const jissekiCumulative = {};
for (let month1 = 10; month1 < 22; month1++) {
const month2 = month1 >= 13 ? month1 - 12 : month1;
for (let i = 40; i <= 160; i += 60) {
const tableId = pad3(month2 + i);
keikakuCumulative[tableId] = [0, 0, 0, 0, 0, 0];
jissekiCumulative[tableId] = [0, 0, 0, 0, 0, 0];
}
}
const goukeiResult = {}; // tableId -> 6行配列
for (let month1 = 10; month1 < 22; month1++) {
const month2 = month1 >= 13 ? month1 - 12 : month1;
for (let i = 0; i <= 120; i += 60) {
const keikakuTableId = pad3(month2 + i);
const jissekiTableId = pad3(month2 + i + 20);
const goukeiTableId = pad3(month2 + i + 40);
const keikakuData = JSON.parse(descriptionHash["Description" + keikakuTableId] || "[]");
const jissekiDataArr = JSON.parse(descriptionHash["Description" + jissekiTableId] || "[]");
const existingGoukei = descriptionHash["Description" + goukeiTableId]
? JSON.parse(descriptionHash["Description" + goukeiTableId])
: JSON.parse(
'[{"0":"当月","1":"計画","2":"","3":"","4":"","5":"","6":"請負金額"},{"0":"","1":"実績","2":"","3":"","4":"","5":"","6":"0%"},{"0":"","1":"差異","2":"","3":"","4":"","5":"","6":""},{"0":"累計","1":"計画","2":"","3":"","4":"","5":"","6":"棟数"},{"0":"","1":"実績","2":"","3":"","4":"","5":"","6":"0%"},{"0":"","1":"差異","2":"","3":"","4":"","5":"","6":""}]'
);
const goukeiData = existingGoukei; // ラベル([0],[1]列、[0][6]/[3][6])を保持したまま数値部分だけ上書きする
const keikakuSum = [0, 0, 0, 0, 0, 0];
const jissekiSum = [0, 0, 0, 0, 0, 0];
for (const row of keikakuData) {
const v4 = parseFloat(String(row[4]).toString().replace(/[¥,]/g, ""));
if (v4 > 0) {
const v6 = parseFloat(String(row[6]).toString().replace(/[¥,]/g, ""));
keikakuSum[1] += v4;
keikakuSum[3] += v6;
if (row[2] != "カウント外") {
keikakuSum[0]++;
keikakuSum[4] += v4;
keikakuSum[5] += v6;
}
}
}
if (keikakuSum[4] > 0 && keikakuSum[5] > 0) {
keikakuSum[2] = Math.floor((keikakuSum[5] / keikakuSum[4]) * 10000) / 100;
}
for (const row of jissekiDataArr) {
const v4 = parseFloat(String(row[4]).toString().replace(/[¥,]/g, ""));
if (v4 > 0) {
const v6 = parseFloat(String(row[6]).toString().replace(/[¥,]/g, ""));
jissekiSum[1] += v4;
jissekiSum[3] += v6;
if (row[2] != "カウント外") {
jissekiSum[0]++;
jissekiSum[4] += v4;
jissekiSum[5] += v6;
}
}
}
if (jissekiSum[4] > 0 && jissekiSum[5] > 0) {
jissekiSum[2] = Math.floor((jissekiSum[5] / jissekiSum[4]) * 10000) / 100;
}
goukeiData[0][2] = keikakuSum[0];
goukeiData[0][3] = keikakuSum[1];
goukeiData[0][4] = keikakuSum[2];
goukeiData[0][5] = keikakuSum[3];
goukeiData[1][2] = jissekiSum[0];
goukeiData[1][3] = jissekiSum[1];
goukeiData[1][4] = jissekiSum[2];
goukeiData[1][5] = jissekiSum[3];
goukeiData[2][2] = jissekiSum[0] - keikakuSum[0];
goukeiData[2][3] = jissekiSum[1] - keikakuSum[1];
goukeiData[2][4] = jissekiSum[2] - keikakuSum[2];
goukeiData[2][5] = jissekiSum[3] - keikakuSum[3];
for (let month3 = month1; month3 < 22; month3++) {
const month2b = month3 >= 13 ? month3 - 12 : month3;
const tableId = pad3(month2b + i + 40);
keikakuCumulative[tableId][0] += keikakuSum[0];
keikakuCumulative[tableId][1] += keikakuSum[1];
keikakuCumulative[tableId][3] += keikakuSum[3];
keikakuCumulative[tableId][4] += keikakuSum[4];
keikakuCumulative[tableId][5] += keikakuSum[5];
keikakuCumulative[tableId][2] =
keikakuCumulative[tableId][1] > 0 ? Math.floor((keikakuCumulative[tableId][5] / keikakuCumulative[tableId][4]) * 10000) / 100 : 0;
jissekiCumulative[tableId][0] += jissekiSum[0];
jissekiCumulative[tableId][1] += jissekiSum[1];
jissekiCumulative[tableId][3] += jissekiSum[3];
jissekiCumulative[tableId][4] += jissekiSum[4];
jissekiCumulative[tableId][5] += jissekiSum[5];
jissekiCumulative[tableId][2] =
jissekiCumulative[tableId][1] > 0 ? Math.floor((jissekiCumulative[tableId][5] / jissekiCumulative[tableId][4]) * 10000) / 100 : 0;
}
goukeiData[3][2] = keikakuCumulative[goukeiTableId][0];
goukeiData[3][3] = keikakuCumulative[goukeiTableId][1];
goukeiData[3][4] = keikakuCumulative[goukeiTableId][2];
goukeiData[3][5] = keikakuCumulative[goukeiTableId][3];
goukeiData[4][2] = jissekiCumulative[goukeiTableId][0];
goukeiData[4][3] = jissekiCumulative[goukeiTableId][1];
goukeiData[4][4] = jissekiCumulative[goukeiTableId][2];
goukeiData[4][5] = jissekiCumulative[goukeiTableId][3];
goukeiData[5][2] = jissekiCumulative[goukeiTableId][0] - keikakuCumulative[goukeiTableId][0];
goukeiData[5][3] = jissekiCumulative[goukeiTableId][1] - keikakuCumulative[goukeiTableId][1];
goukeiData[5][4] = jissekiCumulative[goukeiTableId][2] - keikakuCumulative[goukeiTableId][2];
goukeiData[5][5] = jissekiCumulative[goukeiTableId][3] - keikakuCumulative[goukeiTableId][3];
if (keikakuCumulative[goukeiTableId][1] > 0 && jissekiCumulative[goukeiTableId][1] > 0) {
goukeiData[1][6] = (Math.floor((jissekiCumulative[goukeiTableId][1] / keikakuCumulative[goukeiTableId][1]) * 10000) / 100).toString() + "%";
} else {
goukeiData[1][6] = "0%";
}
if (keikakuCumulative[goukeiTableId][0] > 0 && jissekiCumulative[goukeiTableId][0] > 0) {
goukeiData[4][6] = (Math.floor((jissekiCumulative[goukeiTableId][0] / keikakuCumulative[goukeiTableId][0]) * 10000) / 100).toString() + "%";
} else {
goukeiData[4][6] = "0%";
}
goukeiResult[goukeiTableId] = goukeiData;
// 直接descriptionHashへも書き戻す(次のmonth1ループが最新の計画/実績を参照できるよう。
// ただしkeikaku/jissekiは今回変更しないので実質goukei追記のみ)
descriptionHash["Description" + goukeiTableId] = JSON.stringify(goukeiData);
}
}
return goukeiResult;
}
// summaryRecord()と同一ロジック。goukeiResult(受注/着工/完成 x 12ヶ月)からbulkupsert用Dataを組み立てる
function buildBulkData(goukeiResult, classHashBase) {
const data = [];
for (let i = 1; i <= 12; i++) {
let sortKey;
if (i >= 10) sortKey = pad3(i - 9);
else sortKey = pad3(i + 3);
let count = 1;
for (let j = 40; j <= 160; j += 60) {
const tableId = pad3(i + j);
const goukeiData = goukeiResult[tableId];
const classHash = {
ClassA: classHashBase.ClassA,
ClassB: classHashBase.ClassB,
ClassC: classHashBase.ClassC,
ClassD: classHashBase.ClassD,
ClassE: classHashBase.ClassE,
ClassH: classHashBase.ClassH,
ClassK: sortKey,
ClassY: i,
ClassV: count,
ClassZ: classHashBase.ClassZ,
};
const numHash = {
Num001: goukeiData[0][2],
Num002: goukeiData[0][3],
Num003: goukeiData[0][4],
Num004: goukeiData[0][5],
Num011: goukeiData[1][2],
Num012: goukeiData[1][3],
Num013: goukeiData[1][4],
Num014: goukeiData[1][5],
Num021: goukeiData[3][2],
Num022: goukeiData[3][3],
Num023: goukeiData[3][4],
Num024: goukeiData[3][5],
Num031: goukeiData[4][2],
Num032: goukeiData[4][3],
Num033: goukeiData[4][4],
Num034: goukeiData[4][5],
};
data.push({ ClassHash: classHash, NumHash: numHash });
count++;
}
}
return data;
}
async function processRecord(it, log) {
const resultId = it.ResultId;
const classHashBase = it.ClassHash;
const classE = classHashBase.ClassB; // getJissekiData()同様、自レコードのClassB値を263191のClassEフィルタに使う
const sYear = classHashBase.ClassZ;
const eYear = parseFloat(classHashBase.ClassZ) + 1;
const sDate = `${sYear}/10/01 00:00:00`;
const eDate = `${eYear}/09/30 23:59:59`;
const sDate0 = new Date(sDate);
const eDate0 = new Date(eDate);
const thisMonthLastDay = new Date();
thisMonthLastDay.setMonth(thisMonthLastDay.getMonth() + 1, 0);
let eDate1 = `${thisMonthLastDay.getFullYear()}/${String(thisMonthLastDay.getMonth() + 1).padStart(2, "0")}/${String(
thisMonthLastDay.getDate()
).padStart(2, "0")} 23:59:59`;
const eDate1D = new Date(eDate1);
if (eDate0.getTime() < eDate1D.getTime()) {
eDate1 = eDate;
}
const { jissekiA, jissekiB, jissekiC } = await fetchJisseki(classE, sYear, eYear, sDate, eDate, eDate1);
// Phase A: DescriptionHashへ実績を組み立て(既存の計画等はit.DescriptionHashからコピーして保持)
const descriptionHash = { ...it.DescriptionHash };
for (let i = 1; i <= 12; i++) {
descriptionHash["Description" + pad3(i + 20)] = JSON.stringify(buildJissekiRows(jissekiA[i]));
descriptionHash["Description" + pad3(i + 80)] = JSON.stringify(buildJissekiRows(jissekiB[i]));
descriptionHash["Description" + pad3(i + 140)] = JSON.stringify(buildJissekiRows(jissekiC[i]));
}
// Phase B: 合計(累計込み)を計算し、descriptionHashへ反映(calcGoukei内で書き戻す)
const goukeiResult = calcGoukei(descriptionHash);
// 更新するDescriptionフィールドだけを送信用に抜き出す(実績36 + 合計36 = 72)
const updateDescriptionHash = {};
for (let i = 1; i <= 12; i++) {
for (const off of [20, 80, 140]) updateDescriptionHash["Description" + pad3(i + off)] = descriptionHash["Description" + pad3(i + off)];
for (const off of [40, 100, 160]) updateDescriptionHash["Description" + pad3(i + off)] = descriptionHash["Description" + pad3(i + off)];
}
// Phase C: 507676向けbulkupsertデータ
const bulkData = buildBulkData(goukeiResult, classHashBase);
log(`ResultId ${resultId} (${classHashBase.ClassB}): 受注/着工/完成 実績${["A", "B", "C"].map((x) => "OK").join("/")}, 合計36件, bulkupsert対象${bulkData.length}`);
return { resultId, updateDescriptionHash, bulkData };
}
(async () => {
fs.mkdirSync(modifyDir, { recursive: true });
const ts = new Date().toISOString().replace(/[:.]/g, "-");
const logLines = [];
const log = (s) => {
console.log(s);
logLines.push(s);
};
log(`=== batch-refresh-507570.js 実行開始 (execute=${execute}, only=${onlyId || "全件"}) ===`);
const listRes = await post(`/api/items/${SITE_ID}/get`, { ApiVersion: 1.1, ApiKey: apiKey });
let items = listRes.body.Response.Data;
if (onlyId) items = items.filter((it) => it.ResultId === onlyId);
log(`対象レコード数: ${items.length}`);
const perRecordResults = [];
for (const it of items) {
try {
const r = await processRecord(it, log);
perRecordResults.push(r);
} catch (e) {
log(`ResultId ${it.ResultId}: エラー ${e.message}`);
}
}
const previewPath = path.join(modifyDir, `preview_${ts}.json`);
fs.writeFileSync(
previewPath,
JSON.stringify(
{
targetCount: items.length,
updates: perRecordResults.map((r) => ({ resultId: r.resultId, descriptionKeys: Object.keys(r.updateDescriptionHash), bulkDataCount: r.bulkData.length })),
sampleUpdate: perRecordResults[0],
},
null,
1
)
);
log(`[OK] プレビュー保存: ${previewPath}`);
if (!execute) {
log("[注意] --execute が指定されていないため、送信は行っていません。");
fs.writeFileSync(path.join(modifyDir, `log_${ts}.txt`), logLines.join("\n"));
return;
}
// レコードごとに 507570 update → 507676 bulkupsert(36件)を実行
// (全レコード分をまとめて1回のbulkupsertにすると件数超過でPleasanter側のBulkUpsertMaxに
// 引っかかる恐れがあるため、レコード単位36件ずつに区切って送信する)
let updateOk = 0, updateNg = [];
let bulkOk = 0, bulkNg = [];
for (const r of perRecordResults) {
const updateRes = await post(`/api/items/${r.resultId}/update`, {
ApiVersion: 1.1,
ApiKey: apiKey,
DescriptionHash: r.updateDescriptionHash,
});
if (updateRes.status === 200 && updateRes.body && updateRes.body.StatusCode === 200) updateOk++;
else updateNg.push({ resultId: r.resultId, res: updateRes });
const bulkRes = await post(`/api/items/${SHUKEI_SITE_ID}/bulkupsert`, {
ApiVersion: 1.1,
ApiKey: apiKey,
Keys: ["ClassA", "ClassB", "ClassV", "ClassY", "ClassZ"],
KeyNotFoundCreate: true,
Data: r.bulkData,
});
if (bulkRes.status === 200 && bulkRes.body && bulkRes.body.StatusCode === 200) bulkOk++;
else bulkNg.push({ resultId: r.resultId, res: bulkRes });
log(`ResultId ${r.resultId}: 507570update=${updateRes.status} / 507676bulkupsert=${bulkRes.status} ${bulkRes.body && bulkRes.body.Message ? bulkRes.body.Message : ""}`);
}
log(`507570 update: 成功${updateOk}件 / 失敗${updateNg.length}`);
log(`507676 bulkupsert: 成功${bulkOk}件 / 失敗${bulkNg.length}`);
if (updateNg.length) log("updateNg: " + JSON.stringify(updateNg, null, 1).slice(0, 2000));
if (bulkNg.length) log("bulkNg: " + JSON.stringify(bulkNg, null, 1).slice(0, 2000));
fs.writeFileSync(
path.join(modifyDir, `result_${ts}.json`),
JSON.stringify({ updateOk, updateNg, bulkOk, bulkNg }, null, 1)
);
fs.writeFileSync(path.join(modifyDir, `log_${ts}.txt`), logLines.join("\n"));
})();