/**
* SyncLeave(月別シートと tblLeaveImport が同じブックにある場合に使う:1本で完結)
* 同じブックの「2026_」で始まる月別シート(2026_1 〜 2026_12)をすべて読み取り、
* tblLeaveImport の中身をすべて入れ替える。
* 月別シートは読み取るだけで、書き換えるのは tblLeaveImport だけです。
*/
function main(workbook: ExcelScript.Workbook): string {
const rows = readLeaveRows(workbook);
return writeLeaveRows(workbook, rows);
}
// ===== 設定(ここだけ書き換え) =====
// 読み取るシート:名前が「2026_」で始まるシート(2026_1 〜 2026_12)。来年は "2027_" に変えるだけ
const SHEET_PREFIX = "2026_";
// 人名が始まる行(A6 から)
const START_ROW = 6;
// A列にこの言葉を含む行は、データに入れずに飛ばす(集計の行など。増やしたい場合は "," で追加)
const SKIP_WORDS: string[] = ["休暇数"];
// ---------------------------------------------------------------------
// 月別シート(シート名「2026_9」など、A列=氏名、B列=所属チーム、C列から1日、2日、…)を読み取り、
// 1人1日1行(キー/氏名/氏名キー/所属チーム/日付/区分/年月)の配列にする。
// 読み取るだけで、ブックには何も書き込まない。
// ---------------------------------------------------------------------
function readLeaveRows(workbook: ExcelScript.Workbook): string[][] {
const result: string[][] = [];
const seen: { [key: string]: boolean } = {}; // 同じ人・同じ日の重複を除く
for (const sheet of workbook.getWorksheets()) {
// シート名が「2026_数字」の形のものだけ(それ以外のシートは飛ばす)
const sheetName = sheet.getName().trim();
if (!sheetName.startsWith(SHEET_PREFIX)) continue;
const m = sheetName.match(/^(\d{4})_(\d{1,2})$/);
if (!m) continue;
const year = Number(m[1]);
const month = Number(m[2]);
if (month < 1 || month > 12) continue;
const used = sheet.getUsedRange(true);
if (!used) continue;
// A1 から、使われている最後のセルまでをまとめて読む
const last = used.getLastCell();
const values = sheet.getRangeByIndexes(0, 0, last.getRowIndex() + 1, last.getColumnIndex() + 1).getValues();
const days = new Date(year, month, 0).getDate(); // その月の日数
const ym = `${year}-${pad2(month)}`;
// A6 の行から下を1行ずつ読む
for (let r = START_ROW - 1; r < values.length; r++) {
const name = String(values[r][0]).trim();
if (name === "") continue; // A列が空の行は飛ばす
if (SKIP_WORDS.some((w) => name.indexOf(w) >= 0)) continue; // 「休暇数」などの行は飛ばす
const team = values[r].length > 1 ? String(values[r][1]).trim() : "";
const nameKey = name.replace(/[ ]/g, ""); // 氏名キー:半角・全角スペースを除く
for (let d = 1; d <= days; d++) {
const c = 1 + d; // C列=1日
if (c >= values[r].length) break;
const kind = String(values[r][c]).trim(); // 1日/AM/PM/時間/代休/明け/在宅 など
if (kind === "") continue;
const date = `${ym}-${pad2(d)}`;
const key = `${nameKey}_${date}`;
if (seen[key]) continue;
seen[key] = true;
result.push([key, name, nameKey, team, date, kind, ym]);
}
}
}
// 日付順 → 氏名キー順
result.sort((a, b) => (a[4] + a[2]).localeCompare(b[4] + b[2]));
return result;
}
function pad2(n: number): string {
return (n < 10 ? "0" : "") + n;
}
// ---------------------------------------------------------------------
// 1人1日1行の配列を、このブックのテーブル tblLeaveImport に書き込む(中身をすべて入れ替える)。
// テーブルには「キー/氏名/氏名キー/所属チーム/日付/区分/年月」の列が必要。
// ほかの列(Power Apps が追加する __PowerAppsId__ など)があっても、空欄で書き込むので問題ない。
// ---------------------------------------------------------------------
function writeLeaveRows(workbook: ExcelScript.Workbook, rows: string[][]): string {
const table = workbook.getTable("tblLeaveImport");
if (!table) {
throw new Error("tblLeaveImport というテーブルが見つかりません");
}
const headers = table.getHeaderRowRange().getValues()[0].map((h) => String(h).trim());
const cols = ["キー", "氏名", "氏名キー", "所属チーム", "日付", "区分", "年月"];
const idx = cols.map((c) => headers.indexOf(c));
const missing = cols.filter((c, i) => idx[i] < 0);
if (missing.length > 0) {
throw new Error("tblLeaveImport に次の列がありません:" + missing.join("、"));
}
// いまの行をすべて削除
const count = table.getRowCount();
if (count > 0) {
table.deleteRowsAt(0, count);
}
if (rows.length === 0) {
return "0 件(休暇データがありません)";
}
// テーブルの列の並びに合わせて1行ずつ作る
const data: (string | number | boolean)[][] = rows.map((r) => {
const line: (string | number | boolean)[] = headers.map(() => "");
cols.forEach((c, k) => {
// 年月(2026-10)は日付に変換されないように、先頭に ' を付けて文字として入れる
line[idx[k]] = c === "年月" ? "'" + r[k] : r[k];
});
return line;
});
table.addRows(-1, data);
// 日付の列は「yyyy-mm-dd」の表示にそろえる
const dateCol = table.getColumnByName("日付");
if (dateCol) {
dateCol.getRangeBetweenHeaderAndTotal().setNumberFormat("yyyy-mm-dd");
}
// Power Apps は Excel のテーブルを最大 2000 行までしか読めないため、超えたら知らせる
if (rows.length > 2000) {
return `${rows.length} 件を同期しました(2000 件を超えたため、アプリでは後ろの日付の分が表示されません)`;
}
return `${rows.length} 件を同期しました`;
}