Files
kintone-attendance-system/src/1.園児別出欠簿入力/ExtractHandler.js
2025-09-15 11:09:17 +08:00

382 lines
14 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.

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;
}
}