class ExtractHandler { constructor(headerSpace) { const elements = createBtnGroupArea('extract-action-area', '出欠集計表出力', this.handleExtractData, { btnElId: 'extract-btn', yearElId: 'extract-year', monthElId: 'extract-month', termElId: 'extract-term' }) if (!elements) { return; } headerSpace.appendChild(elements['extract-action-area']); } handleExtractData = async (e, { year, month, term }) => { loading(true, '帳票出力中...'); showError(false); const api = new KintoneRestAPIClient(); // 本アプリからデータを読み取る const records = await this.getRecords(api, year, month, term); if (!records) { // エラー loading(false); return e; } const recordMap = this.buildRecordMap(records); const childMaster = await this.getChildMstRecords(api, year, month, term, Object.keys(recordMap.byId)); if (!childMaster) { // エラー loading(false); return e; } const dayMaster = await this.getDayMstRecords(api, year, month); if (!dayMaster) { // エラー loading(false); return e; } const termTeacher = await this.getTermTeacherMstRecords(api, term); if (termTeacher === undefined) { // エラー loading(false); return e; } const excelName = env["園児別出欠簿入力"].excelName; await createExcelAndDownload({ api, excelName, exportName: getExcelName(excelName, year + month + '_' + term), bizLogic: this.writeExcel({ records, recordMap, childMaster, dayMaster, termTeacher }, term, getJapaneseEraDate(new Date(year, month - 1, 1))), }); loading(false); } getRecords = async (api, year, month, term) => { const firstDate = getFormatDateString(year, month, 1) const lastDate = getFormatDateString(getLastDate(year, month)); try { return await api.record.getAllRecordsWithId({ app: env["園児別出欠簿入力"].appId, condition: `登園日 >= "${firstDate}" and 登園日 <= "${lastDate}" and 学年 in ("${term}")` }); } catch (e) { showError(true, '本アプリのデータ読み取りエラー\n - ' + e); } } getChildMstRecords = async (api, year, month, term, uniqueKeys) => { const date = getJapaneseEraDate(new Date(year, month - 1, 1)) const prevMonth = getJapaneseEraDate(getLastDate(year, month - 1)); const result = {}; try { result['入園'] = await api.record.getAllRecordsWithId({ app: env["園児台帳"].appId, fields: ['性別'], condition: `和暦_入園年月日 in ("${date.era}") and 年_入園年月日 = "${date.year}" and 月_入園年月日 = "${date.month}" and 学年 in ("${term}")` }); result['退園'] = await api.record.getAllRecordsWithId({ app: env["園児台帳"].appId, fields: ['性別'], condition: `和暦_退園年月日 in ("${prevMonth.era}") and 年_退園年月日 = "${prevMonth.year}" and 月_退園年月日 = "${prevMonth.month}" and 学年 in ("${term}")` }); if (uniqueKeys?.length) { result['在籍'] = await api.record.getAllRecordsWithId({ app: env["園児台帳"].appId, fields: ['ユニークキー', '性別'], condition: `ユニークキー in ("${uniqueKeys.join('", "')}")` }); } return result; } catch (e) { showError(true, '園児台帳のデータ読み取りエラー\n - ' + e); } } getDayMstRecords = async (api, yearStr, monthStr) => { let year = Number(yearStr); const month = Number(monthStr); // ※年度=4月~翌年3月までの区切りとする(例:2025年3月→2024年度) if (month < 4) { year--; } try { const data = await api.record.getAllRecordsWithId({ app: env["保育・教育日数マスタ"].appId, fields: ['教育日数' + month, '保育日数' + month, '休日_' + month + '月'], condition: `年度 = "${year}"` }); if (!data || !data[0]) { showError(true, '保育・教育日数マスタのデータが存在しません。'); return; } return data && data[0]; } catch (e) { showError(true, '保育・教育日数マスタのデータ読み取りエラー\n - ' + e); } } getTermTeacherMstRecords = async (api, term) => { try { const data = await api.record.getAllRecordsWithId({ app: env["担任マスタ"].appId, fields: ['担任'], condition: `学年 in ("${term}")` }); return data && data[0] && data[0]['担任']?.value.map((x) => x.name).join('、') || ''; } catch (e) { showError(true, '担任マスタのデータ読み取りエラー\n - ' + e); } } toHolidaySet = (str) => { if (!str) { return new Set(); } return new Set(str.split(',')) } writeExcel = ({ records, recordMap, childMaster, dayMaster, termTeacher }, term, { era, year, westernYear, month }) => { const teachDays = Number(dayMaster['教育日数' + month].value); const careDays = Number(dayMaster['保育日数' + month].value); const holidaySet = this.toHolidaySet(dayMaster['休日_' + month + '月'].value); return async (api, worksheet) => { const baseCells = findCellsInfo(worksheet, ['13', '16', '19', '25', '(担 任)', '1', '番号']); // header updateCell(worksheet, { base: baseCells['13'][0], up: 1 }, era); updateCell(worksheet, { base: baseCells['16'][0], up: 1 }, year + '年'); updateCell(worksheet, { base: baseCells['19'][0], up: 1 }, month + '月'); updateCell(worksheet, { base: baseCells['25'][0], up: 1 }, term); const weekRow = worksheet.getRow(baseCells['1'][0].row + 1); const startCol = baseCells['1'][0].col; const weekStart = new Date(westernYear, month - 1, 1).getDay(); const lastDate = getLastDate(westernYear, month).getDate(); for (let i = 0; i < lastDate; i++) { updateCell(weekRow, { base: { col: startCol + i } }, WEEK[(weekStart + i) % 7]); } if (!records.length) { return; } updateCell(worksheet, { base: baseCells['(担 任)'][0], down: 1 }, termTeacher); fillMainPage(baseCells, worksheet, recordMap); fillFooter(worksheet, recordMap); } function fillMainPage(baseCells, worksheet, recordMap) { baseCells['番号'] = baseCells['番号'].filter((_, index) => index % 2 !== 0); const sortedRecords = Object.values(recordMap.byId).sort((a, b) => parseInt(a['id'], 10) - parseInt(b['id'], 10) ); const lastPage = 2; const baseForTemplate = baseCells['番号'][lastPage - 1]; // 番号 merged 2 rows const pageSize = 15; const totalPages = Math.ceil(sortedRecords.length / pageSize); // make new copy if (totalPages > 2) { const copyPageRowStart = baseForTemplate.row - 2; const copyPageRowEnd = baseForTemplate.row + (pageSize); createCopyFromTemplate(worksheet, { startPage: lastPage + 1, totalPages, copyPageRowStart, copyPageRowEnd, callback: (newPage, rowCount) => { ['番号'].forEach((label) => { const last = baseCells[label][newPage - 2]; baseCells[label].push({ col: last.col, row: last.row + rowCount }); }); } }); } for (let i = 0; i < totalPages; i++) { const childLabelCell = baseCells['番号'][i]; let currentRow = childLabelCell.row + 1; for (let j = 0; j < pageSize; j++) { const index = i * pageSize + j; const recordWrapper = sortedRecords[index]; if (!recordWrapper) { break; } const row = worksheet.getRow(currentRow); const base = { row: currentRow, col: 1 }; // 番号 updateCell(row, { base }, recordWrapper['id']); // 児 童 氏 名 updateCell(row, { base, right: 1 }, recordWrapper['name']); const reasons = []; const sum = { '出席': 0, '出席停止': 0, '病欠': 0, '自欠': 0, '休日欠席': 0 } // 日 recordWrapper.list.forEach((record, i) => { const res = record["出欠"].value; sum[res]++; if (res === '出席') { return; } if (res === '出席停止') { if (record["出席停止理由"].value) { reasons.push(record["出席停止理由"].value); } updateCell(row, { base, right: 2 + i }, '×'); return } // 欠席 if (holidaySet.has(record['登園日'].value)) { sum['休日欠席']++; } updateCell(row, { base, right: 2 + i }, '/'); }) // 出 席 updateCell(row, { base, right: 33 }, sum['出席']); updateCell(row, { base, right: 34 }, sum['出席停止']); // 欠 席 updateCell(row, { base, right: 35 }, sum['病欠']); updateCell(row, { base, right: 36 }, sum['自欠']); // 教育日数 // updateCell(row, { base, right: 37 }, sum['出席'] + sum['出席停止'] - sum['病欠'] - sum['自欠']); updateCell(row, { base, right: 37 }, teachDays - sum['病欠'] - sum['自欠'] + sum['休日欠席']); // 備考 updateCell(row, { base, right: 38 }, reasons.join("\n")); currentRow += 1; } worksheet.getRow(childLabelCell.row + pageSize + 1).addPageBreak(); } } function fillFooter(worksheet, recordMap) { const baseCells = findCellsInfo(worksheet, ['合 計', '保 育 日 数', '男', '女', '欠 席 総 数', '出 席 率', '%']); // 合 計 const totalAreaRow = worksheet.getRow(baseCells['合 計'][0].row); const totalAreaRow2 = worksheet.getRow(baseCells['合 計'][1].row); const totalAreaRow3 = worksheet.getRow(baseCells['合 計'][2].row); const base = { col: 3 }; const dateList = recordMap.sum.list; for (let i = 0; i < dateList.length; i++) { if (dateList[i]) { updateCell(totalAreaRow, { base, right: i }, dateList[i]['出席']); updateCell(totalAreaRow2, { base, right: i }, dateList[i]['出停']); updateCell(totalAreaRow3, { base, right: i }, dateList[i]['欠席']); } } // 保 育 日 数 updateCell(worksheet, { base: baseCells['保 育 日 数'][0], down: 1 }, careDays); // 入 園 数 let list = childMaster['入園']; let [male, female] = groupingBySex(list); updateCell(worksheet, { base: baseCells['男'][0], right: 1 }, male.length); updateCell(worksheet, { base: baseCells['女'][0], right: 1 }, female.length); // 退 園 数 list = childMaster['退園']; [male, female] = groupingBySex(list); updateCell(worksheet, { base: baseCells['男'][1], right: 1 }, male.length); updateCell(worksheet, { base: baseCells['女'][1], right: 1 }, female.length); // 在 籍 数 list = childMaster['在籍']; [male, female] = groupingBySex(list); updateCell(worksheet, { base: baseCells['男'][2], right: 1 }, male.length); updateCell(worksheet, { base: baseCells['女'][2], right: 1 }, female.length); const total = recordMap.sum['欠席'] + recordMap.sum['出席']; if (total) { // 出 席 総 数 updateCell(worksheet, { base: baseCells['欠 席 総 数'][0], left: 1 }, recordMap.sum['出席']); // 欠 席 総 数 updateCell(worksheet, { base: baseCells['出 席 率'][0], left: 1 }, recordMap.sum['欠席']); // 出 席 率 updateCell(worksheet, { base: baseCells['%'][0], right: 1 }, Math.round(recordMap.sum['出席'] / total * 1000) / 10 + '%'); } } } buildRecordMap = (records) => { const recordMap = { sum: { '出席': 0, '欠席': 0, list: [] }, byId: {} } records.forEach((record) => { const dateIndex = Number(record['登園日'].value.split('-')[2]) - 1; const idKey = record['園児ユニークキー'].value; let attendance = recordMap.byId[idKey]; if (!attendance) { attendance = { idKey, id: record['出席番号'].value, name: record['園児名'].value, list: [], }; recordMap.byId[idKey] = attendance } attendance.list[dateIndex] = record; const status = record["出欠"].value; if (status === '出席' || status === '出席停止') { recordMap.sum['出席']++; } else if (status === '病欠' || status === '自欠') { recordMap.sum['欠席']++; } let dateSum = recordMap.sum.list[dateIndex]; if (!dateSum) { dateSum = { '出席': 0, '出停': 0, '欠席': 0, }; recordMap.sum.list[dateIndex] = dateSum } if (status === '出席') { dateSum['出席']++; } else if (status === '出席停止') { dateSum['出停']++; } else if (status === '病欠' || status === '自欠') { dateSum['欠席']++; } }); return recordMap; } static getInstance(headerSpace) { if (!ExtractHandler.instance) { ExtractHandler.instance = new ExtractHandler(headerSpace); } return ExtractHandler.instance; } }