ImportLeave

/**
 * 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} 件を同期しました`;
}

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

日記

前の記事

AI Agent導入提案アプリNew!!