出欠集計表出力
This commit is contained in:
@@ -24,6 +24,9 @@ const env = {
|
||||
"園児台帳": {
|
||||
appId: 16,
|
||||
},
|
||||
"保育・教育日数マスタ": {
|
||||
appId: 41,
|
||||
},
|
||||
"Excelテンプレート": {
|
||||
appId: 46
|
||||
}
|
||||
|
||||
14
src/utils.js
14
src/utils.js
@@ -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];
|
||||
}, [[], []]);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user