382 lines
14 KiB
JavaScript
382 lines
14 KiB
JavaScript
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;
|
||
}
|
||
}
|