出欠集計表出力

This commit is contained in:
2025-02-17 15:00:16 +08:00
parent ccf474bdc6
commit 226853ac37
3 changed files with 333 additions and 3 deletions

View File

@@ -24,6 +24,9 @@ const env = {
"園児台帳": {
appId: 16,
},
"保育・教育日数マスタ": {
appId: 41,
},
"Excelテンプレート": {
appId: 46
}

View File

@@ -1,5 +1,7 @@
const Kuc = Kucs['1.19.0'];
const WEEK = ['日', '月', '火', '水', '木', '金', '土']
const monthItems = Array.from({ length: 12 }, (_, i) => {
const month = "" + (i + 1);
return { label: month, value: month.padStart(2, '0') };
@@ -170,6 +172,7 @@ function checkInputData(map, { btnLabel, yearElId, monthElId, dateElId, classElI
}
function getLastDate(year, month) {
// month は 1-12 の数値で入力する
return new Date(year, month, 0);
}
@@ -486,3 +489,14 @@ function fillApproveArea(baseCells, worksheet, record) {
worksheet.getCell(signRow, baseCells['園長'][0].col + i).value = (record[signLabels[i]]?.value || '');
}
}
function groupingBySex(list) {
return list.reduce(([male, female], record) => {
if (record['性別'].value === '男') {
return [male.concat(record), female];
} else if (record['性別'].value === '女') {
return [male, female.concat(record)];
}
return [male, female];
}, [[], []]);
}

View File

@@ -1,4 +1,5 @@
class ExtractHandler {
constructor(headerSpace) {
const elements = createBtnGroupArea('extract-action-area', '出欠集計表出力', this.handleExtractData, {
btnElId: 'extract-btn',
@@ -12,9 +13,321 @@ class ExtractHandler {
headerSpace.appendChild(elements['extract-action-area']);
}
handleExtractData(e, { year, month, className }) {
const fileName = getExcelName(env["園児別出欠簿入力"], year + month + '_' + className + '組');
console.log(fileName);
handleExtractData = async (e, { year, month, className }) => {
loading(true, '帳票出力中...');
showError(false);
const api = new KintoneRestAPIClient();
// 本アプリからデータを読み取る
const records = await this.getRecords(api, year, month, className);
if (!records) {
// エラー
loading(false);
return e;
}
const recordMap = this.buildRecordMap(records);
const childMaster = await this.getChildMstRecords(api, year, month, className, 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 excelName = env["園児別出欠簿入力"].excelName;
await createExcelAndDownload({
api,
excelName,
exportName: getExcelName(excelName, year + month + '_' + className + '組'),
bizLogic: this.writeExcel({ records, recordMap, childMaster, dayMaster }, className, getJapaneseEraDate(new Date(year, month - 1, 1))),
});
loading(false);
}
getRecords = async (api, year, month, className) => {
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 ("${className}")`
});
} catch (e) {
showError(true, '本アプリのデータ読み取りエラー\n - ' + e);
}
}
getChildMstRecords = async (api, year, month, className, 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 ("${className}")`
});
result['退園'] = await api.record.getAllRecordsWithId({
app: env["園児台帳"].appId,
fields: ['性別'],
condition: `和暦_退園年月日 in ("${prevMonth.era}") and 年_退園年月日 = "${prevMonth.year}" and 月_退園年月日 = "${prevMonth.month}" and クラス in ("${className}")`
});
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],
condition: `年度 = "${year}"`
});
return data && data[0];
} catch (e) {
showError(true, '保育・教育日数マスタのデータ読み取りエラー\n - ' + e);
}
}
writeExcel = ({ records, recordMap, childMaster, dayMaster }, className, { era, year, westernYear, month }) => {
const teachDays = Number(dayMaster['教育日数' + month].value);
const careDays = Number(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 }, className + '組');
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 }, records[0]['担任']?.value || ''); // TODO force use records[0]?
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) => a['idKey'].localeCompare(b['idKey']));
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,
}
// 日
recordWrapper.list.forEach((record, i) => {
const res = record["出欠"].value;
sum[res]++;
if (res === '出席') {
return;
}
if (res === '出席停止' && record["出席停止理由"].value) {
reasons.push(record["出席停止理由"].value);
}
updateCell(row, { base, right: 2 + i }, res === '出席停止' ? '×' : '');
})
// 出 席
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['自欠']);
// 備考
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) {