update process

This commit is contained in:
2025-09-15 09:47:59 +08:00
parent 1d06e25626
commit c5cf6c0c22
48 changed files with 163 additions and 59 deletions

View File

@@ -0,0 +1,183 @@
class BatchCreateHandler {
constructor(headerSpace) {
const elements = createBtnGroupArea('batch-action-area', '出欠簿一括作成', this.handleCreateData, {
btnElId: 'batch-btn',
yearElId: 'batch-year',
monthElId: 'batch-month',
dateElId: 'batch-date',
defaultThisMonth: true,
})
if (!elements) {
return;
}
headerSpace.appendChild(elements['batch-action-area']);
}
handleCreateData = async (e, { year, month, date }) => {
loading(true, '出欠簿一括作成中...');
showError(false);
const api = new KintoneRestAPIClient();
// 園児台帳アプリからデータを読み取る
const masterRecords = await this.getMasterRecords(api);
if (!masterRecords) {
// エラー
loading(false);
return;
}
const today = new Date(year, month - 1, date);
const todayString = getFormatDateString(today);
const records = this.generateRecords(today, todayString, masterRecords);
// console.log(records);
const result = await this.createData(api, records, todayString);
// showSuccess(true, "出欠簿一括作成完了");
if (result) {
this.showSuccessDialog(result);
}
loading(false);
}
showSuccessDialog(result) {
const contentEl = document.createElement('div');
contentEl.style.fontSize = '16px';
if (result.updateResult && result.insertResult) {
contentEl.innerHTML = `${result.insertResult.records.length}件のデータを更新し、${result.insertResult.records.length}件のデータを新規作成しました。`
} else if (result.updateResult) {
contentEl.innerHTML = `${result.updateResult.records.length}件のデータを更新しました。`;
} else if (result.insertResult) {
contentEl.innerHTML = `${result.insertResult.records.length}件のデータを生成しました。`;
} else {
contentEl.innerHTML = `データの更新はありません。既に最新の状態です。`
}
showDialog({
title: '出欠簿一括作成完了',
content: contentEl,
ok: '更新して確認',
cancel: false,
onClose: () => { location.reload() }
});
}
generateRecords = (today, todayString, masterRecords) => {
return masterRecords.reduce((acc, masterRecord) => {
if (this.needCreateData(masterRecord, today)) {
acc.push(this.createRecord(masterRecord, todayString));
}
return acc;
}, []);
}
needCreateData = (record, today) => {
// 当日の日付が、退園年月日「年_退園年月日」「月_退園年月日」「日_退園年月日」を参照より前の値
// ※同日の場合は取得条件に合致とする(取得する)、退園年月日に値が入っていない場合は無条件に取得
const era = record["和暦_退園年月日"].value;
const lastYear = record["年_退園年月日"].value;
const lastMonth = record["月_退園年月日"].value;
const lastDate = record["日_退園年月日"].value;
if (!era || !lastYear || !lastMonth || !lastDate) {
return true;
}
const todayObj = new Date(today);
todayObj.setHours(0, 0, 0, 0);
const lastDateObj = new Date(convertToWesternYear(Number(lastYear), era), Number(lastMonth) - 1, Number(lastDate));
lastDateObj.setHours(0, 0, 0, 0);
return todayObj.getTime() <= lastDateObj.getTime();
}
createRecord = (record, todayString) => {
return {
'登園日': { 'value': todayString },
'園児ユニークキー': { 'value': record['ユニークキー'].value },
'担任': { 'value': record['担当者名'].value.map((x)=>x.name).join('、') },
'保護者ログインID': { 'value': record['保護者ログインID'].value },
}
}
getMasterRecords = async (api) => {
try {
return await api.record.getAllRecordsWithId({
app: env["園児台帳"].appId,
fields: ['ユニークキー', '担当者名', "保護者ログインID", "和暦_退園年月日", "年_退園年月日", "月_退園年月日", "日_退園年月日"],
});
} catch (e) {
showError(true, '園児台帳アプリのデータ読み取りエラー\n - ' + e);
}
}
createData = async (api, records, todayString) => {
try {
const alreadyGeneratedKeys = await this.getAlreadyGeneratedKeys(api, records, todayString);
// console.log(alreadyGeneratedKeys);
if (!alreadyGeneratedKeys) {
// エラー
return;
}
const param = {
app: env["園児別出欠簿入力"].appId,
}
const insert = [];
const update = [];
records.forEach(record => {
const id = alreadyGeneratedKeys[record["園児ユニークキー"].value]
if (id) {
update.push({
id,
record
});
} else {
insert.push(record);
}
});
let insertResult;
if (insert.length) {
param.records = insert;
insertResult = await api.record.addAllRecords(param);
}
let updateResult;
if (update.length) {
param.records = update;
// 本アプリの当日データでテーブルを上書きし、対応する園児データがない行を【削除】します
updateResult = await api.record.updateAllRecords(param);
}
return {
insertResult,
updateResult
}
} catch (e) {
showError(true, '本アプリのデータ作成失敗\n - ' + e);
}
};
getAlreadyGeneratedKeys = async (api, records, todayString) => {
try {
const needCreateUniqueKeys = records.map((each) => `"${each["園児ユニークキー"].value}"`).join(',')
const alreadyGeneratedResult = await api.record.getAllRecordsWithId({
app: env["園児別出欠簿入力"].appId,
fields: ["園児ユニークキー"],
condition: `登園日 = "${todayString}" and 園児ユニークキー in (${needCreateUniqueKeys})`
})
return alreadyGeneratedResult.reduce((map, each) => {
map[each["園児ユニークキー"].value] = each["$id"].value;
return map;
}, {});
} catch (e) {
showError(true, '本アプリのデータ読み取りエラー\n - ' + e);
}
}
static getInstance(env, headerSpace) {
if (!BatchCreateHandler.instance) {
BatchCreateHandler.instance = new BatchCreateHandler(env, headerSpace);
}
return BatchCreateHandler.instance;
}
}

View File

@@ -0,0 +1,381 @@
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;
}
}

View File

@@ -0,0 +1,215 @@
class Link1Handler {
constructor(headerSpace) {
const elements = createBtnGroupArea('link-1-action-area', '0,1歳日誌データ連携', this.handleLink, {
btnElId: 'link-1-btn',
yearElId: 'link-1-year',
monthElId: 'link-1-month',
})
if (!elements) {
return;
}
headerSpace.appendChild(elements['link-1-action-area']);
}
handleLink = async (e, { year, month }) => {
loading(true, '日誌データ連携中...');
showError(false);
const api = new KintoneRestAPIClient();
// 本アプリからデータを読み取る
const currentAppRecords = await this.getRecords(api, year, month);
if (!currentAppRecords) {
// エラー
loading(false);
return;
}
// console.log(currentAppRecords);
const recordsWrapper = await this.generateRecords(api, year, month, currentAppRecords);
// console.log(recordsWrapper);
const result = await this.insertOrUpdateData(api, recordsWrapper);
if (result) {
// showSuccess(true, "日誌データ連携作成完了");
this.showSuccessDialog(result, year, month);
}
loading(false);
}
showSuccessDialog(result, year, month) {
const dateString = year + '年' + month + '月';
const contentEl = document.createElement('div');
contentEl.style.fontSize = '16px';
if (result.updateResult && result.insertResult) {
contentEl.innerHTML = `${dateString}${result.updateResult.records.length}人の園児の日誌を更新し、${result.insertResult.records.length}人の園児の日誌を新規作成しました。`
} else if (result.updateResult) {
contentEl.innerHTML = `${dateString}${result.updateResult.records.length}人の園児の日誌が更新されました。`;
} else if (result.insertResult) {
contentEl.innerHTML = `${dateString}${result.insertResult.records.length}人の園児の日誌が生成されました`;
} else {
contentEl.innerHTML = `${dateString}の園児の日誌に生成するデータはありませんでした。`
}
showDialog({
title: '日誌データ連携作成完了',
content: contentEl,
ok: 'アプリへ行く',
cancel: '閉じる',
onOk: () => { window.open(`${location.origin}/k/${env["0,1歳日誌出力用"].appId}/`) },
});
}
insertOrUpdateData = async (api, recordsWrapper) => {
try {
const param = {
app: env["0,1歳日誌出力用"].appId,
}
let insertResult;
if (recordsWrapper['insert'].length) {
param.records = recordsWrapper['insert'];
insertResult = await api.record.addAllRecords(param);
}
let updateResult;
if (recordsWrapper['update'].length) {
param.records = recordsWrapper['update'];
// 本アプリの当日データでテーブルを上書きし、対応する園児データがない行を【削除】します
updateResult = await api.record.updateAllRecords(param);
}
return {
recordsWrapper,
insertResult,
updateResult
};
} catch (e) {
showError(true, '日誌データ連携作成失敗\n - ' + e);
}
};
generateRecords = async (api, year, month, records) => {
const generatedRecordIdMap = await this.getGeneratedRecordIdMap(api, year, month);
if (!generatedRecordIdMap) {
// エラー
loading(false);
return;
}
const kidsMap = {};
records.forEach((record) => {
const uniqueKey = record['園児ユニークキー'].value;
let map = kidsMap[uniqueKey];
if (!map) {
map = {
existId: generatedRecordIdMap[uniqueKey],
list: [],
'年': { 'value': Number(year) },
'月': { 'value': Number(month) },
'学年': { 'value': record['学年'].value },
'クラス': { 'value': record['クラス'].value },
'出席番号': { 'value': record['出席番号'].value },
'園児名': { 'value': record['園児名'].value },
'園児ユニークキー': { 'value': uniqueKey },
'帳票出力用_テーブル': { 'value': undefined },
};
kidsMap[uniqueKey] = map
}
record['date'] = Number(record['登園日'].value.split('-')[2]);
map.list.push(record);
});
// console.log(generatedRecordIdMap);
// console.log(kidsMap);
const result = {
'update': [],
'insert': []
}
Object.values(kidsMap).forEach((recordWrapper) => {
const subtable = Array.from({ length: 31 }, (_e, i) => ({
value: {
'日付': { 'value': i + 1 },
}
}));
recordWrapper.list.forEach((record) => {
subtable[record['date'] - 1] = {
value: {
'日付': { 'value': record['date'] },
'出欠': { 'value': record['出欠'].value },
'降園': { 'value': record['帰園時刻'].value },
'検温時刻1': { 'value': record['検温時刻1'].value },
'体温1': { 'value': record['体温1'].value },
'検温時刻2': { 'value': record['検温時刻2'].value },
'体温2': { 'value': record['体温2'].value },
'検温時刻3': { 'value': record['検温時刻3'].value },
'体温3': { 'value': record['体温3'].value },
'食事量': { 'value': record['食事量_結合'].value },
'排便': { 'value': record['排便'].value },
'睡眠開始時間1': { 'value': record['睡眠開始時間1'].value },
'睡眠終了時間1': { 'value': record['睡眠終了時間1'].value },
'睡眠開始時間2': { 'value': record['睡眠開始時間2'].value },
'睡眠終了時間2': { 'value': record['睡眠終了時間2'].value },
'保護者から': { 'value': record['保護者から'].value },
'園での様子_伝達事項': { 'value': record['園での様子_伝達事項'].value },
'評価反省': { 'value': record['評価反省'].value },
}
}
})
recordWrapper['帳票出力用_テーブル'].value = subtable;
const id = recordWrapper['existId'];
delete recordWrapper['list'];
delete recordWrapper['existId'];
if (id) {
result['update'].push({
id,
record: recordWrapper
});
} else {
result['insert'].push(recordWrapper);
}
});
return result;
}
getRecords = async (api, year, month) => {
const firstDate = getFormatDateString(year, month, 1)
const lastDate = getFormatDateString(getLastDate(year, month));
try {
return await api.record.getAllRecordsWithId({
app: env["園児別出欠簿入力"].appId,
condition: `学年 in ("0歳児", "1歳児") and 登園日 >= "${firstDate}" and 登園日 <= "${lastDate}"`
});
} catch (e) {
showError(true, '本アプリのデータ読み取りエラー\n - ' + e);
loading(false);
}
}
getGeneratedRecordIdMap = async (api, year, month) => {
try {
const result = await api.record.getAllRecordsWithId({
app: env["0,1歳日誌出力用"].appId,
fields: ["$id", "園児ユニークキー"],
condition: `年 = ${year} and 月 = ${month}`
});
const map = {}
result.forEach((record) => {
const uniqueKey = record['園児ユニークキー'].value;
map[uniqueKey] = record['$id'].value;
});
return map;
} catch (e) {
showError(true, '日誌データ読み取りエラー\n - ' + e);
loading(false);
}
}
static getInstance(headerSpace) {
if (!Link1Handler.instance) {
Link1Handler.instance = new Link1Handler(headerSpace);
}
return Link1Handler.instance;
}
}

View File

@@ -0,0 +1,207 @@
class Link2Handler {
constructor(headerSpace) {
const elements = createBtnGroupArea('link-2-action-area', '2歳以上日誌データ連携', this.handleLink, {
btnElId: 'link-2-btn',
yearElId: 'link-2-year',
monthElId: 'link-2-month',
dateElId: 'link-2-date',
defaultThisMonth: true,
})
if (!elements) {
return;
}
headerSpace.appendChild(elements['link-2-action-area']);
}
handleLink = async (e, { year, month, date }) => {
loading(true, '日誌データ連携中...');
showError(false);
const api = new KintoneRestAPIClient();
const dateString = getFormatDateString(year, month, date)
// 本アプリからデータを読み取る
const currentAppRecords = await this.getRecords(api, dateString);
if (!currentAppRecords) {
// エラー
loading(false);
return;
}
const groupedRecords = this.groupingAndSort(currentAppRecords);
const records = [];
for (const item of groupedRecords) {
records.push(this.generateRecord(dateString, item));
}
const result = await this.insertOrUpdateData(api, records, dateString);
if (result) {
// showSuccess(true, "日誌データ連携作成完了");
this.showSuccessDialog(result, year, month, date);
}
loading(false);
}
showSuccessDialog(result, year, month, date) {
const dateString = year + '年' + month + '月' + date + '日';
const contentEl = document.createElement('div');
contentEl.style.fontSize = '16px';
if (!result.size) {
contentEl.innerHTML = `${dateString}の園児別テーブルに追加するデータはありませんでした。`;
} else if (!result.insert) {
contentEl.innerHTML = `${dateString}の園児別テーブルに${result.size}件のデータを再作成しました。`;
} else if (!result.update) {
contentEl.innerHTML = `${dateString}の日誌は生成しましたが、園児別テーブルに${result.size}件のデータを追加しました。`;
} else {
contentEl.innerHTML = `${dateString}${result.update}人の園児の日誌を更新し、${result.insert}人の園児の日誌を新規作成しました。`
}
showDialog({
title: '日誌データ連携作成完了',
content: contentEl,
ok: 'アプリへ行く',
cancel: '閉じる',
dataHolder: result,
onOk: () => { window.open(`${location.origin}/k/${env["2歳以上日誌出力用"].appId}/`) },
});
}
getRecords = async (api, dateString) => {
try {
return await api.record.getAllRecordsWithId({
app: env["園児別出欠簿入力"].appId,
fields: ['クラス', '学年', "園児名", "園での様子_伝達事項", "評価反省"],
condition: `学年 not in ("", "0歳児", "1歳児") and 登園日 = "${dateString}"`
});
} catch (e) {
showError(true, '本アプリのデータ読み取りエラー\n - ' + e);
loading(false);
}
}
groupingAndSort = (data) => {
const groupedByGrade = {};
// 1. group by 学年
data.forEach((record) => {
const grade = record['学年'].value;
if (!groupedByGrade[grade]) {
groupedByGrade[grade] = [];
}
groupedByGrade[grade].push(record);
});
// 2. sort by クラス
const classOrder = {};
classItems.forEach((item, index) => {
classOrder[item.value] = index;
});
for (const grade in groupedByGrade) {
groupedByGrade[grade].sort((a, b) => {
return classOrder[a["クラス"].value] - classOrder[b["クラス"].value];
});
}
// 3. return a list
return termItems.reduce((acc, term) => {
const grade = term.value;
// Skip 0歳児 and 1歳児
if (grade === "0歳児" || grade === "1歳児") {
return acc;
}
acc.push({
grade: grade,
items: groupedByGrade[grade] || []
});
return acc;
}, []);
}
generateRecord = (todayString, groupedRecords) => {
return {
'登園日': { 'value': todayString },
'学年': { 'value': groupedRecords.grade },
'園児別テーブル': {
'value': groupedRecords.items.map((record) => {
return {
'value': {
'クラス名': { 'value': record['クラス'].value },
'名前': { 'value': record['園児名'].value },
'子どもの様子': { 'value': record['園での様子_伝達事項'].value },
'反省評価_園児別テーブル': { 'value': record['評価反省'].value },
}
}
})
}
};
}
insertOrUpdateData = async (api, records, dateString) => {
const results = [];
const generatedRecords = await api.record.getAllRecordsWithId({
app: env["2歳以上日誌出力用"].appId,
fields: ["$id", "学年"],
condition: `登園日 = "${dateString}"`
})
const existIdMap = generatedRecords.reduce((map, item) => {
const grade = item['学年'].value;
const id = item['$id'].value;
map[grade] = id;
return map;
}, {});
for (const record of records) {
try {
const generatedRecordId = existIdMap[record["学年"].value];
const param = {
app: env["2歳以上日誌出力用"].appId,
record
}
const result = {
id: generatedRecordId,
type: generatedRecordId ? 'update' : 'insert',
size: record["園児別テーブル"].value.length,
};
let awaitResult;
if (result.type === 'update') {
param.id = generatedRecordId;
// 本アプリの当日データでテーブルを上書きし、対応する園児データがない行を【削除】します
awaitResult = await api.record.updateRecord(param);
} else {
awaitResult = await api.record.addRecord(param);
}
Object.assign(result, awaitResult);
results.push(result);
} catch (e) {
showError(true, '日誌データ連携作成失敗\n - ' + e);
}
}
return results.reduce((result, item) => {
// Sum all sizes
result.size += item.size;
// Sum based on type
if (item.type === 'insert') {
result.insert += item.size;
} else if (item.type === 'update') {
result.update += item.size;
}
return result;
}, {
size: 0, // Total sum of all sizes
insert: 0, // Sum of insert sizes
update: 0 // Sum of update sizes
});;
};
static getInstance(headerSpace) {
if (!Link2Handler.instance) {
Link2Handler.instance = new Link2Handler(headerSpace);
}
return Link2Handler.instance;
}
}

View File

@@ -0,0 +1,140 @@
(function () {
"use strict";
// ------------------- 詳細画面表示時の処理 -------------------
kintone.events.on('app.record.detail.show', function (event) {
// 「登園/帰園」ボタン
const area = kintone.app.record.getSpaceElement('header-clocking-btn-area');
const clockIn = createBtn('clock-in', '登園', dateToFieldInDetailIn());
const clockOut = createBtn('clock-out', '帰園', dateToFieldInDetailOut());
area.appendChild(clockIn);
area.appendChild(clockOut);
hideSpaceField(['clock-in-btn-area', 'clock-out-btn-area']);
// 「伝達事項」
if (!isBaby(event.record['学年'].value)) {
kintone.app.record.setFieldShown('伝達事項', false);
}
return event;
});
function isBaby(grade) {
return grade === "0歳児" || grade === "1歳児";
}
//PVC追加
function dateToFieldInDetailIn() {
return async function (e) {
await new KintoneRestAPIClient().record.updateRecord({
app: kintone.app.getId(),
id: kintone.app.record.getId(),
record: {
'登園時刻': {
value: getCurrentTime()
},
"出欠": {
value: "出席"
}
}
});
location.reload();
}
}
function dateToFieldInDetailOut() {
return async function (e) {
await new KintoneRestAPIClient().record.updateRecord({
app: kintone.app.getId(),
id: kintone.app.record.getId(),
record: {
'帰園時刻': {
value: getCurrentTime()
}
}
});
location.reload();
}
}
// ------------------- 印刷画面表示時の処理 -------------------
kintone.events.on('app.record.print.show', (event) => {
// 「登園/帰園」ボタン
hideSpaceField(['clock-in-btn-area', 'clock-out-btn-area']);
// 「伝達事項」
if (!isBaby(event.record['学年'].value)) {
kintone.app.record.setFieldShown('伝達事項', false);
}
return event;
});
// ------------------- 編集画面表示時の処理 -------------------
kintone.events.on(['app.record.create.show', 'app.record.edit.show'], function (event) {
// 「登園/帰園」ボタン
const clockIn = createBtn('clock-in', '登園', dateToFieldInEditIn());
kintone.app.record.getSpaceElement('clock-in-btn-area').appendChild(clockIn);
const clockOut = createBtn('clock-out', '帰園', dateToFieldInEditOut());
kintone.app.record.getSpaceElement('clock-out-btn-area').appendChild(clockOut);
// 「伝達事項」
if (!isBaby(event.record['学年'].value)) {
kintone.app.record.setFieldShown('伝達事項', false);
}
return event;
});
// -------------------「園での様子_入力者」の処理 -------------------
const _DATA_HOLDER_MAP = {}
// 「園での様子_伝達事項」はテキストエリア複数行入力欄であり、app.record.edit.changeイベントをサポートしていません
// したがって、データを事前に保存しておき、保存submit時のタイミングで更新を行います。
kintone.events.on(['app.record.index.edit.show', 'app.record.create.show', 'app.record.edit.show'], function (event) {
_DATA_HOLDER_MAP['isEditorChanged'] = false;
_DATA_HOLDER_MAP['prevSituationValue'] = event.record['園での様子_伝達事項']['value'] || '';
return event;
});
kintone.events.on(['app.record.create.change.園での様子_入力者', 'app.record.edit.change.園での様子_入力者', 'app.record.index.edit.change.園での様子_入力者'], function(event) {
_DATA_HOLDER_MAP['isEditorChanged'] = true;
return event;
});
kintone.events.on(['app.record.create.submit', 'app.record.edit.submit', 'app.record.index.edit.submit'], function (event) {
const current = event.record['園での様子_伝達事項']['value'] || '';
if (_DATA_HOLDER_MAP['prevSituationValue'] != current && !_DATA_HOLDER_MAP['isEditorChanged']) {
event.record['園での様子_入力者']['value'] = [kintone.getLoginUser()];
}
return event;
});
//PVC追加
function dateToFieldInEditIn() {
return function (e) {
var record = kintone.app.record.get();
record['record']['登園時刻']['value'] = getCurrentTime();
record['record']['出欠']['value'] = "出席";
kintone.app.record.set(record);
}
}
function dateToFieldInEditOut() {
return function (e) {
var record = kintone.app.record.get();
record['record']['帰園時刻']['value'] = getCurrentTime();
kintone.app.record.set(record);
}
}
function getCurrentTime() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
})();

View File

@@ -0,0 +1,3 @@
(function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)});
//# sourceMappingURL=FileSaver.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,496 @@
/** @copyright 2018 Planning Village Corporation Co., Ltd. */
(function () {
'use strict';
// [IE11] Polyfill of Object.values
if (!Object.values) {
const ownKeys = (
(typeof Reflect === 'object' && typeof Reflect.ownKeys === 'function') ? Reflect.ownKeys :
// @ts-ignore
(typeof Object.getOwnPropertySymbols === 'function') ? (function (O) {
// @ts-ignore
return Object.getOwnPropertyNames(O).concat(Object.getOwnPropertySymbols(O));
}) :
Object.getOwnPropertyNames);
// @ts-ignore
const reduce = Function.bind.call(Function.call, Array.prototype.reduce);
// @ts-ignore
const isEnumerable = Function.bind.call(Function.call, Object.prototype.propertyIsEnumerable);
// @ts-ignore
const concat = Function.bind.call(Function.call, Array.prototype.concat);
//@ts-ignore
Object.values = function values(O) {
//@ts-ignore
return reduce(ownKeys(O), function (v, k) {
return concat(v, typeof k === 'string' && isEnumerable(O, k) ? [O[k]] : [])
}, [])
};
}
// [IE11] Polyfill of Number.isNaN
if (!Number.isNaN) {
Number.isNaN = function (value) {
return value !== null && (value != value || +value != value);
};
}
// [IE11] Polyfill of String.prototype.startsWith
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
//@ts-ignore
value: function (search, rawPos) {
var pos = rawPos > 0 ? rawPos | 0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
}
// [IE11] Polyfill of String.prototype.includes
if (!String.prototype.includes) {
String.prototype.includes = function (search, start) {
'use strict';
//@ts-ignore
if (search instanceof RegExp) {
throw TypeError('first argument must not be a RegExp');
}
if (start === undefined) {
start = 0;
}
return this.indexOf(search, start) !== -1;
};
}
// [IE11] Polyfill of Array.prototype.findIndex
if (!Array.prototype.findIndex) {
// @ts-ignore
Array.prototype.findIndex = function (predicate) {
if (this === null) {
throw new TypeError('Array.prototype.findIndex called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return i;
}
}
return -1;
};
}
//@ts-ignore
window.pvc = window.pvc || {};
//@ts-ignore
window.pvc.lib = window.pvc.lib || {};
//@ts-ignore
window.pvc.lib.exceljsUtil = (function () {
const exceljs = {
/**
* ワークブックの blob を読み取ります。
* @param {Blob} blob Blob
* @return {Promise<ExcelJS.Workbook>} ワークブック
*/
loadWorkbookBlob: function (blob) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (e) {
// @ts-ignore
resolve(e.target.result);
};
reader.onerror = function (e) {
reject(e);
}
reader.onabort = function (e) {
reject(e);
}
reader.readAsArrayBuffer(blob);
}).then(function (arrayBuffer) {
// 非公式のメンバー load を使用
// @ts-ignore
return new ExcelJS.Workbook().xlsx.load(arrayBuffer);
});
},
/**
* ワークブックを Blob に変換します。
* @param {ExcelJS.Workbook} workbook ワークブック
* @return {Promise<Blob>} Blob
*/
saveWorkbookToBlob: function (workbook) {
// writeBuffer の戻り値 ExcelJS.Buffer はブラウザ環境では Uint8Array となる
return workbook.xlsx.writeBuffer().then(function (ua) {
return new Blob([ua], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
});
},
/**
* 行をコピーします。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number} topRowNumber コピー元の開始行番号(1から開始)
* @param {number} [bottomRowNumber] コピー元の終了行番号(1から開始)。省略時は開始行番号と同じ値となります。
* @returns {pvc.lib.exceljsUtil.CopiedRowsInfo} 行のコピー情報
*/
copyRows: function (worksheet, topRowNumber, bottomRowNumber) {
const _bottomRowNumber = (bottomRowNumber == null ? topRowNumber : bottomRowNumber);
if (topRowNumber < 0 || _bottomRowNumber < topRowNumber) {
throw new Error('コピー元の領域とが不正です。');
}
// コピー元の領域の情報を収集
const rows = [];
for (let r = topRowNumber; r <= _bottomRowNumber; r++) {
const row = worksheet.getRow(r);
const cells = [];
for (let c = 1; c <= worksheet.columnCount; c++) {
const cell = row.getCell(c);
cells.push({
value: cell.value,
style: cell.style,
});
}
rows.push({
cells: cells,
height: row.height,
// 非公式のメンバー style を使用
// @ts-ignore
style: row.style,
});
}
// コピー元の領域の内部に収まるマージを収集
const merges = deepCopyObject(getWorksheetMerges(worksheet).filter(function (merge) {
return (topRowNumber <= merge.top && merge.bottom <= _bottomRowNumber);
}).map(function (merge) {
return {
top: merge.top - topRowNumber,
right: merge.right,
bottom: merge.bottom - topRowNumber,
left: merge.left,
};
}));
return deepCopyObject({
rows: rows,
merges: merges
});
},
/**
* 指定された開始行を起点に、コピーした行を貼り付けます。
* コピー元の領域と領域外の両方にまたがるセルのマージは貼り付けされません。
* コピー先の領域を一部でも含むマージは全て解除されます。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number} topRowNumber コピー先の開始行番号(1から開始)
* @param {pvc.lib.exceljsUtil.CopiedRowsInfo} copiedRowsInfo 行のコピー情報
*/
pasteRows: function (worksheet, topRowNumber, copiedRowsInfo) {
const bottomRowNumber = topRowNumber + copiedRowsInfo.rows.length - 1;
if (topRowNumber < 0) {
throw new Error('コピー先の領域とが不正です。');
}
// コピー先の行のマージを解除(コピー先の領域を一部でも含むマージは全て解除)
getWorksheetMerges(worksheet).filter(function (merge) {
return (topRowNumber <= merge.bottom && merge.top <= bottomRowNumber);
}).forEach(function (merge) {
worksheet.unMergeCells(merge.range);
});
// マージをペースト
copiedRowsInfo.merges.forEach(function (mergeInfo) {
worksheet.mergeCells(
topRowNumber + mergeInfo.top,
mergeInfo.left,
topRowNumber + mergeInfo.bottom,
mergeInfo.right);
});
// セルをペースト
copiedRowsInfo.rows.forEach(function (rowInfo, i) {
const row = worksheet.getRow(topRowNumber + i);
row.height = rowInfo.height;
// 非公式のメンバー style を使用
// @ts-ignore
row.style = rowInfo.style;
for (let c = 1; c <= worksheet.columnCount; c++) {
const cell = row.getCell(c);
const cellInfo = rowInfo.cells[c - 1];
if (cellInfo) {
cell.value = cellInfo.value;
cell.style = cellInfo.style;
} else {
exceljs.clearCell(cell);
}
}
});
},
/**
* 指定された開始行を起点に、コピーした行を挿入します。
* コピー元の領域と領域外の両方にまたがるセルのマージは貼り付けされません。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number} topRowNumber コピー先の開始行番号(1から開始)
* @param {pvc.lib.exceljsUtil.CopiedRowsInfo} copiedRowsInfo 行のコピー情報
*/
insertRows: function (worksheet, topRowNumber, copiedRowsInfo) {
const bottomRows = (
topRowNumber <= worksheet.rowCount ?
exceljs.copyRows(worksheet, topRowNumber, worksheet.rowCount) :
null);
exceljs.pasteRows(worksheet, topRowNumber, copiedRowsInfo);
if (bottomRows) {
exceljs.pasteRows(worksheet, topRowNumber + copiedRowsInfo.rows.length, bottomRows);
}
},
/**
* 列をコピーします。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number | string} leftColumn コピー元の開始列の列番号(1から開始)または列ラベル。終了列の引数を省略して、代わりに "A:B" の形式でコピー元の列の範囲を指定することも可能。
* @param {number | string} [rightColumn] コピー元の終了列の列番号(1から開始)または列ラベル。省略時は開始列と同じとみなされます(開始列を "A:B" の形式で指定された場合を除く)。
* @returns {pvc.lib.exceljsUtil.CopiedColumnsInfo} 列のコピー情報
*/
copyColumns: function (worksheet, leftColumn, rightColumn) {
if (rightColumn == null) {
rightColumn = leftColumn;
if (typeof leftColumn === 'string') {
const sp = leftColumn.split(':');
if (sp.length === 2) {
leftColumn = sp[0];
rightColumn = sp[1];
}
}
}
const leftColumnNumber = ((typeof leftColumn === 'number') ? leftColumn : exceljs.columnLetterToColumnNumber(leftColumn));
const rightColumnNumber = ((typeof rightColumn === 'number') ? rightColumn : exceljs.columnLetterToColumnNumber(rightColumn));
if (leftColumnNumber < 0 || rightColumnNumber < leftColumnNumber) {
throw new Error('コピー元の領域とが不正です。');
}
// コピー元の領域の情報を収集
const columns = [];
for (let c = leftColumnNumber; c <= rightColumnNumber; c++) {
const column = worksheet.getColumn(c);
const cells = [];
for (let r = 1; r <= worksheet.rowCount; r++) {
const cell = worksheet.getRow(r).getCell(c);
cells.push({
value: cell.value,
style: cell.style,
});
}
columns.push({
cells: cells,
width: column.width,
style: column.style,
});
}
// コピー元の領域の内部に収まるマージを収集
const merges = deepCopyObject(getWorksheetMerges(worksheet).filter(function (merge) {
return (leftColumnNumber <= merge.left && merge.right <= rightColumnNumber);
}).map(function (merge) {
return {
top: merge.top,
right: merge.right - leftColumnNumber,
bottom: merge.bottom,
left: merge.left - leftColumnNumber,
};
}));
return {
columns: columns,
merges: merges
};
},
/**
* 指定された開始列を起点に、コピーした列を貼り付けます。
* コピー元の領域と領域外の両方にまたがるセルのマージは貼り付けされません。
* コピー先の領域を一部でも含むマージは全て解除されます。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number | string} leftColumn コピー先の開始列の列番号(1から開始)または列ラベル
* @param {pvc.lib.exceljsUtil.CopiedColumnsInfo} copiedColumnsInfo 列のコピー情報
*/
pasteColumns: function (worksheet, leftColumn, copiedColumnsInfo) {
const leftColumnNumber = ((typeof leftColumn === 'number') ? leftColumn : exceljs.columnLetterToColumnNumber(leftColumn));
const rightColumnNumber = leftColumnNumber + copiedColumnsInfo.columns.length - 1;
if (leftColumnNumber < 0) {
throw new Error('コピー先の領域とが不正です。');
}
// コピー先の列のマージを解除(コピー先の領域を一部でも含むマージは全て解除)
getWorksheetMerges(worksheet).filter(function (merge) {
return (leftColumnNumber <= merge.right && merge.left <= rightColumnNumber);
}).forEach(function (merge) {
worksheet.unMergeCells(merge.range);
});
// マージをペースト
copiedColumnsInfo.merges.forEach(function (mergeInfo) {
worksheet.mergeCells(
mergeInfo.top,
leftColumnNumber + mergeInfo.left,
mergeInfo.bottom,
leftColumnNumber + mergeInfo.right);
});
// セルをペースト
copiedColumnsInfo.columns.forEach(function (columnInfo, i) {
const column = worksheet.getColumn(leftColumnNumber + i);
column.width = columnInfo.width;
column.style = columnInfo.style;
for (let r = 1; r <= worksheet.rowCount; r++) {
const cell = worksheet.getRow(r).getCell(leftColumnNumber + i);
const cellInfo = columnInfo.cells[r - 1];
if (cellInfo) {
cell.value = cellInfo.value;
cell.style = cellInfo.style;
} else {
exceljs.clearCell(cell);
}
}
});
},
/**
* 指定された開始列を起点に、コピーした列を挿入します。
* コピー元の領域と領域外の両方にまたがるセルのマージは貼り付けされません。
* @param {ExcelJS.Worksheet} worksheet ワークシート
* @param {number | string} leftColumn コピー先の開始列の列番号(1から開始)または列ラベル
* @param {pvc.lib.exceljsUtil.CopiedColumnsInfo} copiedRowsInfo 列のコピー情報
*/
insertColumns: function (worksheet, leftColumn, copiedRowsInfo) {
const leftColumnNumber = ((typeof leftColumn === 'number') ? leftColumn : exceljs.columnLetterToColumnNumber(leftColumn));
const rightColumns = (
leftColumnNumber <= worksheet.columnCount ?
exceljs.copyColumns(worksheet, leftColumnNumber, worksheet.columnCount) :
null);
exceljs.pasteColumns(worksheet, leftColumnNumber, copiedRowsInfo);
if (rightColumns) {
exceljs.pasteColumns(worksheet, leftColumnNumber + copiedRowsInfo.columns.length, rightColumns);
}
},
/**
* セルをクリアします。
* @param {ExcelJS.Cell} cell セル
*/
clearCell: function (cell) {
cell.value = null;
/// @ts-ignore
cell.style = cell._mergeStyle(cell._row.style, cell._column.style, {});
},
/**
* 列ラベルから列番号(1から開始)を取得します。
* @param {string} columnLetter 列ラベル
* @return {number} 列番号(1から開始)
*/
columnLetterToColumnNumber: function (columnLetter) {
const letter = columnLetter.toUpperCase();
const l = letter.length;
const charCodeA = 65;
let result = 0;
for (let i = 0, j = l - 1; i < l; i++, j--) {
result += (letter.charCodeAt(j) - charCodeA + 1) * (i === 0 ? 1 : Math.pow(26, i));
}
return result;
},
/**
* 日付のセルの値を作成します。
* @param {Date} date
* @returns {any}
*/
createDateCellValue: function (date) {
// UTC にしないと正しく Excel に反映されない
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
},
deepCopyObject: deepCopyObject,
};
/**
* ワークシートのマージを取得します。
* @param {ExcelJS.Worksheet} worksheet ワークシート
*/
function getWorksheetMerges(worksheet) {
// 非公式のメンバー _merges を使用
// @ts-ignore
const mergesDic = worksheet._merges;
return Object.keys(mergesDic).map(function (address) {
return mergesDic[address];
});
}
/**
* オブジェクのコピーします。ディープコピーとなります。
* @template T
* @param {T} obj
* @returns {T}
*/
function deepCopyObject(obj) {
return copyObject(obj);
//@ts-ignore
function copyObject(obj) {
return (
obj == null ? null :
Array.isArray(obj) ? copyArray(obj) :
isDate(obj) ? copyDate(obj) :
isDic(obj) ? copyDic(obj) :
obj);
}
//@ts-ignore
function copyArray(array) {
//@ts-ignore
return array.map(function (item) {
return copyObject(item);
});
}
//@ts-ignore
function isDate(obj) {
return (obj != null && Object.prototype.toString.call(obj) === '[object Date]');
}
//@ts-ignore
function copyDate(date) {
return (isNaN(date) ? null : new Date(date.getTime()));
}
//@ts-ignore
function isDic(obj) {
return (obj != null && typeof obj === 'object');
}
//@ts-ignore
function copyDic(dic) {
let result = {};
for (let key in dic) {
//@ts-ignore
result[key] = copyObject(dic[key]);
}
return result;
}
}
return exceljs;
}());
})();

View File

@@ -0,0 +1,22 @@
(function () {
"use strict";
const APP_ENV = env["園児別出欠簿入力"];
kintone.events.on("app.record.index.show", (event) => {
const headerSpace = getHeaderSpace('single-label-line');
if (event.viewId === APP_ENV.view.linkFor0to1) {
Link1Handler.getInstance(headerSpace);
return event;
}
if (event.viewId === APP_ENV.view.linkForOthers) {
Link2Handler.getInstance(headerSpace);
return event;
}
BatchCreateHandler.getInstance(headerSpace);
ExtractHandler.getInstance(headerSpace);
});
})();

View File

@@ -0,0 +1,19 @@
(function () {
"use strict";
const statusFieldMap = {
'指導教諭確認中': '担任',
'主幹確認中': '指導',
'園長確認中': '主幹',
'完了': '園長'
}
kintone.events.on("app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
})();