Compare commits

...

10 Commits

47 changed files with 3254 additions and 505 deletions

View File

@@ -0,0 +1,197 @@
### 2025年8月22日 15:48
**发件人:** 清水優太(PVC)
**收件人:** 馬 小哲;薛 家豪
**主题:** 【横山台こども園様】仕様書をお送りします
アリコーン
馬様、薛様
いつもお世話になっております。
プランニングヴィレッヂの清水です。
先ほどは横山台こども園様の開発引き継ぎに
ご参加いただきありがとうございました。
画面共有していた仕様書及び
スペーステンプレートをお送りいたしますので、
ご確認いただけますと幸いです。
現在確認中の内容については、
固まり次第追ってご連絡いたします。
何かご不明な点等ございましたらご連絡ください。
引き続きよろしくお願いいたします。
---
### 2025年8月25日 16:38
**发件人:** 馬 小哲
**收件人:** 清水優太(PVC)
**抄送:** 小山嘉久(PVC);薛 家豪
**主题:** RE: 【横山台こども園様】仕様書をお送りします
プランニングヴィレッヂ
清水様
いつもお世話になっております。
アリコーンの馬です。
先日は仕様のご説明ありがとうございました。
ご送付いただく仕様書を拝見し、以下の点をご確認ください。
1. 「園児別出欠簿入力」の「園での様子入力者」自動入力機能について:
編集者による変更を許可するかご確認:
・変更不可の場合、「園での様子入力者」欄を編集画面で非表示とする必要があるか?
・変更可の場合、「園での様子入力者」変更がない場合のみ、保存時にログインユーザーで上書きする仕様を想定しています。
2. 「個別配慮」の輸出順について:
現在の仕様は「1.学年, 2出席番号」ですが、クラスが異なる園児で出席番号が重複する可能性がありますか
重複がある場合、「学年→クラス→出席番号」へのソート条件変更をご検討ください。
3. 「個別配慮」の入力単位変更について:
・月次から年次への変更仕様はまだ確定していない認識でよろしいでしょうか?
・年次に変更する場合、Excelレイアウト変更が必要がありますか
・また、輸出件数が園児数×月数になるため、パフォーマンスへの影響も懸念されます。
4. 「保育・教育日数マスタ」の休日自動取得について:
・保存時に休日データのソートおよび重複除去が必要か
※年間休日が120日以上のため、ソートされないと操作性に影響する可能性があります。
5. 一括承認機能について:
現在、仕様が未確定という認識でよろしいでしょうか?
6. 現在ご提示いただいている見積工数について、下記の対応工数が含まれているかご確認ください。
・「個別配慮」の年次対応
・一括承認機能の追加開発
含まれていない場合、追加工数として見積可能かどうかもあわせてご教示ください。
以上、ご確認のほどよろしくお願いいたします。
引き続きよろしくお願いいたします。
---
### 2025年8月25日 18:09
**发件人:** 清水優太(PVC)
**收件人:** 馬 小哲
**抄送:** 小山嘉久(PVC);薛 家豪
**主题:** Re: 【横山台こども園様】仕様書をお送りします
アリコーン
馬様
いつもお世話になっております。
プランニングヴィレッヂの清水です。
仕様の方ご確認いただきありがとうございます。
ご質問につきまして下記回答いたします。
1. 「園児別出欠簿入力」の「園での様子入力者」自動入力機能について
編集者による手修正の考慮が抜けており申し訳ございません。
こちらは後者の「変更可の場合~」を採用してください。
2. 「個別配慮」の輸出順について
同じ学年で複数のクラスは存在しないので、学年→出席番号のソート順で問題ございません。
3. 「個別配慮」の入力単位変更について
すみません、こちらの仕様はまだお客様検討中です。
年次の場合もフォーマットの変更はございません。
また、Excelの輸出件数は変わらないため、パフォーマンスはさほど影響ないと考えますが、
これについては私が間違っているかもしれません。
いずれにしても本機能は実装いただけますと助かります。
4. 「保育・教育日数マスタ」の休日自動取得について
ソートや重複除去が可能でしたら。実装いただけると大変ありがたいです。
5. 一括承認機能について
こちらもお客様確認中ですが、動作としては概ね以下のような形になります。
作業者=ログインユーザーで絞りこんだ一覧にボタンを配置し、押下で一括承認する仕組みです。
6. 現在ご提示いただいている見積工数について、下記の対応工数が含まれているかご確認ください
こちらは双方とも含んだ形で見積もり工数をお出ししております。
資料の方が曖昧な記載で申し訳ございません。
以上になります。
ご確認の程よろしくお願いいたします。
清水
---
### 2025年8月28日 10:44
**发件人:** 馬 小哲
**收件人:** 清水優太(PVC)
**抄送:** 小山嘉久(PVC);薛 家豪
**主题:** RE: 【横山台こども園様】仕様書をお送りします
プランニングヴィレッヂ
清水様
いつもお世話になっております。
アリコーンの馬です。
以下の点についてご確認させていただきたく、ご連絡いたしました。
1. 教育日数の計算式について、
現在は「教育日数-欠勤日数(病欠+自欠)」となっておりますが、
今回の仕様変更により、欠勤日が休日の場合は除外することになっております。
こちらの対応に問題はございませんが、Excel表に記載されている欠勤病欠自欠の合計数についても、
休日分を除外する必要があるかご教示いただけますでしょうか。
2. 保育・教育日数マスタについて
「教育日数+休日数」がその月の月日数と一致している必要があるか、ご確認をお願いいたします。
3. 台帳の園児名変更について
台帳の園児名が変更された場合、園児別出欠簿入力以外に、
以下のアプリの園児名も同様に変更する必要があるか、ご確認いただけますでしょうか。
- 0・1歳日誌出力用
- 2歳以上日誌出力用
- 個別配慮
お忙しいところ恐縮ですが、ご確認のほどよろしくお願いいたします。
---
### 2025年8月29日 09:50
**发件人:** 清水優太(PVC)
**收件人:** 馬 小哲
**抄送:** 小山嘉久(PVC);薛 家豪
**主题:** Re: 【横山台こども園様】仕様書をお送りします
アリコーン
馬様
いつもお世話になっております。
プランニングヴィレッヂの清水です。
ご質問いただきました件につきまして下記回答いたします。
> 1.教育日数の計算式について
欠勤の合計につきましては今までの計算式(病欠+自欠)で構いません。
> 2.保育・教育日数マスタについて
そこまでのチェックは必要ございません。
> 3.台帳の園児名変更について
確かに他アプリも全て変更されるのが望ましいですが、
下記3点の理由により実装しなくて良いと考えます。
①お客様の方でアプリをいくつか追加する予定があり、
 本開発進行中に作成されるとそれも対応しなくてはならなくなる可能性あり
②日誌については再輸出してもらえばよく、
個別配慮は数が多くないので手直しにて対応可能
③そもそもそれほど費用(工数)をいただいていない。
以上になります。
また、お客様に確認中の事項につきまして、
現状まだ回答をいただいておりませんため、
未確定部分の仕様書の送付は来週になるかもしれません。
ご迷惑をお掛けし申し訳ございませんが、よろしくお願いいたします。
清水

View File

@@ -1,6 +0,0 @@
(function () {
"use strict";
addApproveFlowAction(true);
})();

View File

@@ -1,6 +0,0 @@
(function () {
"use strict";
addApproveFlowAction(true);
})();

View File

@@ -1,95 +0,0 @@
.btn-group-area {
display: flex;
/* margin-left: 2.5em; */
align-items: flex-end;
}
.btn-group-area .label {
margin: 0 4px;
}
.btn-group-area .input {
padding-left: 8px;
}
.btn-group-area .year {
--kuc-text-input-width: calc(4em + 16px);
--kuc-text-input-height: 48px;
}
.btn-group-area .year input {
text-align: center;
}
.btn-group-area .month,
.btn-group-area .date,
.btn-group-area .term {
--kuc-combobox-toggle-width: calc(2em + 16px);
--kuc-combobox-toggle-height: 48px;
}
.btn-group-area .term {
--kuc-combobox-toggle-width: calc(3em + 16px);
}
.btn-group-area .month input[class^='kuc-combobox'],
.btn-group-area .date input[class^='kuc-combobox'],
.btn-group-area .term input[class^='kuc-combobox'] {
text-align: center;
padding-right: 8px;
}
.btn-group-area .month input + div[class$="icon"],
.btn-group-area .date input + div[class$="icon"],
.btn-group-area .term input + div[class$="icon"] {
display: none;
}
.action-btn {
margin: 0 8px;
}
.customized-record-header-space {
padding-top: 16px;
padding-left: 8px;
}
.customized-record-header-space > .btn-group-area {
padding: 0;
margin: 0;
}
.kintone-app-headermenu-space {
height: auto;
display: inline-flex;
}
#user-js-header-clocking-btn-area {
margin: 4px 0;
text-align: center;
}
#user-js-header-clocking-btn-area > .action-btn {
--kuc-button-width: 6em
}
#user-js-header-clocking-btn-area > .action-btn:first-child {
margin-right: 1em;
}
#user-js-clock-in-btn-area > .action-btn,
#user-js-clock-out-btn-area > .action-btn {
--kuc-mobile-button-height: 35px;
margin: .5em 12px;
}
.kuc--has-spinner {
position: relative;
}
div[class^='kuc-spinner'][class$='__spinner'] {
margin-top: 89px;
height: calc(100% - 89px);
position: absolute;
--kuc-spinner-text-color: #3498db;
--kuc-spinner-loader-color: #3498db;
}
div[class^='kuc-spinner'][class$='mask'] {
top: 89px;
position: absolute;
background-color: white;
}

View File

@@ -1,206 +0,0 @@
const Kuc = Kucs['1.19.0'];
let notificationEl;
function getHeaderSpace(className, isDetailPage) {
const headerSpace = kintone.mobile.app.getHeaderSpaceElement();
if (!headerSpace) {
throw new Error('このページではヘッダー要素が利用できません。');
};
const _className = isDetailPage ? 'customized-record-header-space' : 'customized-header-space';
headerSpace.className += (' ' + _className + ' ' + className);
headerSpace.parentElement.className += (' ' + _className + '-wrapper ' + className);
return headerSpace;
}
const classItems = [
{ label: "にじ", value: "にじ" },
{ label: "ほし", value: "ほし" },
{ label: "つき", value: "つき" },
{ label: "ゆり", value: "ゆり" },
]
const termItems = [
{ label: "0歳児", value: "0歳児" },
{ label: "1歳児", value: "1歳児" },
{ label: "2歳児", value: "2歳児" },
{ label: "3歳児", value: "3歳児" },
{ label: "4歳児", value: "4歳児" },
{ label: "5歳児", value: "5歳児" },
]
function createBtnGroupArea(groupId, btnLabel, btnOnClick, { btnElId = false, yearElId = false, monthElId = false, dateElId = false, termElId = false, defaultThisMonth = false, }) {
const result = {};
if (document.getElementById(groupId)) {
return;
}
const btnGroupAreaEl = document.createElement('div');
btnGroupAreaEl.id = groupId;
btnGroupAreaEl.className = 'btn-group-area'
result[groupId] = btnGroupAreaEl;
if (yearElId) {
const yearEl = new Kuc.MobileText({
value: "" + new Date().getFullYear(),
id: yearElId,
label: '年',
className: 'year input'
});
result[yearElId] = yearEl;
btnGroupAreaEl.appendChild(yearEl);
}
if (monthElId) {
const monthEl = new Kuc.MobileDropdown({
value: defaultThisMonth ? monthItems[new Date().getMonth()].value : undefined,
id: monthElId,
className: 'month input',
label: '月',
items: monthItems,
});
result[monthElId] = monthEl;
btnGroupAreaEl.appendChild(monthEl);
}
if (dateElId) {
const dateEl = new Kuc.MobileDropdown({
value: dateItems[new Date().getDate() - 1].value,
id: dateElId,
items: dateItems,
label: '日',
className: "date input",
});
result[dateElId] = dateEl;
btnGroupAreaEl.appendChild(dateEl);
}
if (termElId) {
const termEl = new Kuc.MobileDropdown({
id: termElId,
items: termItems,
className: "term input",
label: '学年',
});
result[termElId] = termEl;
btnGroupAreaEl.appendChild(termEl);
}
const btnEl = new Kuc.MobileButton({
text: btnLabel,
type: "submit",
className: "action-btn",
id: btnElId,
});
result[btnElId] = btnEl;
btnEl.addEventListener('click', (e) => {
showError(false);
const checkResult = checkInputData(result, { btnLabel, yearElId, monthElId, dateElId, termElId });
if (checkResult) {
btnOnClick(e, checkResult);
}
});
btnGroupAreaEl.appendChild(btnEl);
return result;
}
function checkInputData(map, { btnLabel, yearElId, monthElId, dateElId, termElId }) {
const year = yearElId && map[yearElId].value;
const month = monthElId && map[monthElId].value;
const date = dateElId && (map[dateElId].value === 'end' ? getLastDate(year, month).getDate() : map[dateElId].value);
const term = termElId && map[termElId].value;
const errorMsgs = [];
if (yearElId) {
const yearRegex = /^\d+$/;
if (!yearRegex.test(year)) {
errorMsgs.push(' · 年は整数で入力してください。');
}
}
if (monthElId && !month) {
errorMsgs.push(' · 月を選択してください。');
}
if (dateElId && !date) {
errorMsgs.push(' · 日を選択してください。');
}
if (termElId && !term) {
errorMsgs.push(' · 学年を選択してください。');
}
if (errorMsgs.length > 0) {
showError(true, btnLabel + 'エラー\n' + errorMsgs.join('\n'))
return;
}
return {
year,
month,
date,
term,
}
}
function createBtn(id, label, onClick) {
const btnEl = new Kuc.MobileButton({
text: label,
type: "submit",
className: "action-btn",
id,
});
btnEl.addEventListener('click', onClick);
return btnEl;
}
function hideSpaceField(ids) {
ids.forEach(id => {
const area = kintone.mobile.app.record.getSpaceElement(id);
area.parentElement.style.minWidth = '0';
area.parentElement.style.display = 'none';
});
}
const statusFieldMap = {
'指導教諭確認中': '担任',
'主幹確認中': '指導',
'園長確認中': '主幹',
'完了': '園長'
}
function addApproveFlowAction() {
return kintone.events.on("mobile.app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
}
function showError(show, text) {
if (show) {
buildNotification('danger', text);
notificationEl.open();
console.error(text);
} else {
notificationEl && notificationEl.close();
}
}
function buildNotification(type, text, duration = -1) {
const param = {
type,
text,
duration
}
if (!notificationEl) {
notificationEl = new Kuc.MobileNotification(param);
} else {
Object.assign(notificationEl, param);
}
}

View File

@@ -222,6 +222,18 @@ function convertToWesternYear(year, era) {
return warekiStartYear[era] + year - 1; return warekiStartYear[era] + year - 1;
} }
function getYearRange() {
const today = new Date();
let year = today.getFullYear();
if (today.getMonth() + 1 < 4) {
year -= 1;
}
return {
'start': getFormatDateString(year, 4, 1),
'end': getFormatDateString(year + 1, 3, 31)
}
}
function getFormatDateString(dateObjOrYear, month, date) { function getFormatDateString(dateObjOrYear, month, date) {
let year = dateObjOrYear; let year = dateObjOrYear;
if (typeof dateObjOrYear === "object" && !month && !date) { if (typeof dateObjOrYear === "object" && !month && !date) {

View File

@@ -0,0 +1,77 @@
#legend {
width: 100%;
top: -24px;
position: absolute;
text-align: right;
font-size: 13px;
}
#legend .box {
display: inline-block;
width: 14px;
height: 14px;
background: #ffe3e6;
margin-right: 4px;
vertical-align: middle;
}
/* カレンダーエリア */
#calendar {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 12px;
box-sizing: border-box;
}
.month {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-template-rows: auto repeat(6, 1fr);
border: 1px solid #aaa;
border-radius: 4px;
}
.month .title {
grid-column: 1/8;
text-align: center;
font-weight: bold;
padding: 4px 0;
}
.month .cell {
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
}
#calendar:not(.readonly) .month .cell:not(.header):not(:empty) {
cursor: pointer;
}
.month .cell:not(:empty) {
padding: 2px 0;
}
.month .sun {
color: #e74c3c;
}
.month .sat {
color: #2196f3;
}
.month .cell.selected {
background: #ffe3e6;
}
#calendar:not(.readonly) .month .cell:not(.header):not(.selected):not(:empty):hover {
background: #ffe3e677;
}
#calendar:not(.readonly) .month .cell.selected:hover {
background: #ffd3d7;
}

View File

@@ -0,0 +1,312 @@
(() => {
'use strict'
let _CALENDAR
// レコード詳細/新規作成/編集画面表示時にカレンダーを初期化
kintone.events.on(
['app.record.detail.show', 'app.record.create.show', 'app.record.edit.show'],
(event) => {
kintone.app.record.setFieldShown('休日の生データ', false);
initCalendarArea(
event.record,
getHolidayMap(event.record),
event.type === 'app.record.detail.show'
)
return event
}
)
// 年度フィールド変更時に各月の休日をクリアしてカレンダーを再描画
kintone.events.on(
[
'app.record.create.change.年度',
'app.record.edit.change.年度',
'app.record.index.edit.change.年度'
],
(event) => {
forEachRawFields((field) => {
event.record[field].value = ''
})
if (event.type !== 'app.record.index.edit.change.年度') {
initCalendarArea(event.record)
}
return event
}
)
// 一覧編集画面では各月の休日フィールドを編集不可にする
kintone.events.on(
['app.record.index.edit.show'],
(event) => {
forEachRawFields((field) => {
event.record[field].disabled = true
})
return event
}
)
// レコード保存時にカレンダーで選択した休日を各月フィールドへ反映
kintone.events.on(
['app.record.create.submit', 'app.record.edit.submit'],
(event) => {
const resultMap = _CALENDAR.getSelectedDatesMap()
forEachRawFields((field, i) => {
event.record[field].value = resultMap[i].join(',')
})
return event
}
)
/* --------------------------------------------------
* 初期化・データ準備
* -------------------------------------------------- */
// カレンダー表示エリアを初期化
function initCalendarArea(record, map = {}, readonly = false) {
const calendarAreaEl = kintone.app.record.getSpaceElement('calendar-area')
calendarAreaEl.innerHTML = ''
const year = record.年度.value
if (isYearString(year)) {
_CALENDAR = createCalendar(Number(year), map, readonly)
calendarAreaEl.appendChild(_CALENDAR.fragment)
}
}
// 既存レコードに保存されている休日を取得
function getHolidayMap(record) {
const res = {}
forEachRawFields((field, i) => {
const str = record[field].value
if (!str) {
return
}
res[i] = new Set(str.split(','))
})
return res
}
/* --------------------------------------------------
* ユーティリティ
* -------------------------------------------------- */
// 各月フィールドをループ
const forEachRawFields = (callback) => {
for (let i = 1; i <= 12; i++) {
callback(`休日_${i}`, i)
}
}
// ゼロパディング
const padZero = (n) => (n < 10 ? '0' + n : n)
// Date → yyyy-MM-dd
const toYmd = (d) => `${d.getFullYear()}-${padZero(d.getMonth() + 1)}-${padZero(d.getDate())}`
// 土日判定
const isWeekend = (d) => d.getDay() === 0 || d.getDay() === 6
// 年度文字列の妥当性チェック
function isYearString(str) {
// 文字列かつ空でない
if (typeof str !== 'string' || str.length === 0) {
return false
}
// 数字のみ
if (!/^\d+$/.test(str)) {
return false
}
// 4桁
if (str.length !== 4) {
return false
}
// 1900-3000 の範囲
const year = parseInt(str, 10)
return year >= 1900 && year <= 3000
}
/* --------------------------------------------------
* カレンダー生成
* -------------------------------------------------- */
const createCalendar = (year, highlightMap, readonly) => {
const fragment = document.createDocumentFragment()
/* ----- 図例 ----- */
const legend = document.createElement('div')
legend.id = 'legend'
legend.innerHTML = '<span class="box"></span>休日'
fragment.appendChild(legend)
/* ----- カレンダーコンテナ ----- */
const calendar = document.createElement('div')
calendar.id = 'calendar'
if (readonly) {
calendar.classList.add('readonly')
}
fragment.appendChild(calendar)
/* ----- 内部状態 ----- */
const selectedSet = new Set()
const months = buildMonths(year)
months.forEach((m) => {
calendar.appendChild(buildMonth(m))
})
/* ---------- 月情報生成 ---------- */
function buildMonths(year) {
return Array.from({ length: 12 }, (_, i) => {
const monthIndex = 3 + i // 0 基準月で 4 月始まり
const date = new Date(year, monthIndex, 1)
return {
displayMonth: date.getMonth() + 1,
year: date.getFullYear(),
month: date.getMonth(),
label: `${date.getFullYear()}${date.getMonth() + 1}`
}
})
}
/* ---------- 月単位カレンダー生成 ---------- */
function buildMonth({ displayMonth, year, month, label }) {
const monthBox = document.createElement('div')
monthBox.className = 'month'
buildTitle(monthBox, label)
buildHeader(monthBox)
const first = new Date(year, month, 1)
buildPadBlocksStart(first, monthBox)
buildDateBlocks(year, month, displayMonth, monthBox)
buildPadBlocksEnd(monthBox)
return monthBox
}
function buildTitle(monthBox, label) {
const title = document.createElement('div')
title.className = 'title'
title.textContent = label
monthBox.appendChild(title)
}
function buildHeader(monthBox) {
['日', '月', '火', '水', '木', '金', '土'].forEach((day, i) => {
const cell = document.createElement('div')
cell.className = 'cell header'
addWeekendClass(cell, i)
cell.textContent = day
monthBox.appendChild(cell)
})
}
function addWeekendClass(cell, week) {
if (week === 0) {
cell.classList.add('sun')
} else if (week === 6) {
cell.classList.add('sat')
}
}
function buildPadBlocksStart(first, monthBox) {
const startDayInWeek = first.getDay()
for (let i = 0; i < startDayInWeek; i++) {
const cell = document.createElement('div')
cell.className = 'cell'
monthBox.appendChild(cell)
}
}
function buildDateBlocks(year, month, displayMonth, monthBox) {
const last = new Date(year, month + 1, 0)
const highlights = highlightMap[displayMonth] || null
for (let day = 1; day <= last.getDate(); day++) {
const dateObj = new Date(year, month, day)
const ymd = toYmd(dateObj)
const cell = document.createElement('div')
cell.className = 'cell'
cell.textContent = day
cell.dataset.date = ymd
addWeekendClass(cell, dateObj.getDay())
// 初期選択判定
let shouldSelect = false
if (highlights === null) {
shouldSelect = isWeekend(dateObj)
} else if (highlights instanceof Set) {
shouldSelect = highlights.has(ymd)
}
if (shouldSelect) {
cell.classList.add('selected')
selectedSet.add(ymd)
}
// 編集可モードのみクリックイベントを追加
if (!readonly) {
cell.addEventListener('click', () => {
cell.classList.toggle('selected')
if (cell.classList.contains('selected')) {
selectedSet.add(ymd)
} else {
selectedSet.delete(ymd)
}
})
}
monthBox.appendChild(cell)
}
}
function buildPadBlocksEnd(monthBox) {
// 7×7 マスに揃えるための埋め草
while (monthBox.children.length < 49) {
const cell = document.createElement('div')
cell.className = 'cell'
monthBox.appendChild(cell)
}
}
/* ---------- 選択済み日付取得 ---------- */
function getSelectedDatesMap() {
const res = {}
months.forEach(({ month }) => {
res[month + 1] = []
})
// 選択済み日付を振り分け
selectedSet.forEach((ymd) => {
const d = new Date(ymd)
const displayMonth = d.getMonth() + 1
res[displayMonth].push(ymd)
})
// 選択が無い月は全土日を自動追加
Object.keys(res).forEach((monthKey) => {
if (res[monthKey].length === 0) {
const monthInfo = months.find((m) => m.displayMonth == monthKey)
if (monthInfo) {
const year = monthInfo.year
const month = monthInfo.month
const lastDay = new Date(year, month + 1, 0).getDate()
for (let day = 1; day <= lastDay; day++) {
const dateObj = new Date(year, month, day)
if (isWeekend(dateObj)) {
res[monthKey].push(toYmd(dateObj))
}
}
}
}
})
return res
}
return {
fragment,
getSelectedDatesMap
}
}
})()

View File

@@ -1,12 +0,0 @@
(function () {
"use strict";
kintone.events.on("mobile.app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
})();

View File

@@ -1,12 +0,0 @@
(function () {
"use strict";
kintone.events.on("mobile.app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
})();

View File

@@ -1,6 +0,0 @@
(function () {
"use strict";
addApproveFlowAction(true);
})();

View File

@@ -108,9 +108,13 @@ class ExtractHandler {
try { try {
const data = await api.record.getAllRecordsWithId({ const data = await api.record.getAllRecordsWithId({
app: env["保育・教育日数マスタ"].appId, app: env["保育・教育日数マスタ"].appId,
fields: ['教育日数' + month, '保育日数' + month], fields: ['教育日数' + month, '保育日数' + month, '休日_' + month + '月'],
condition: `年度 = "${year}"` condition: `年度 = "${year}"`
}); });
if (!data || !data[0]) {
showError(true, '保育・教育日数マスタのデータが存在しません。');
return;
}
return data && data[0]; return data && data[0];
} catch (e) { } catch (e) {
showError(true, '保育・教育日数マスタのデータ読み取りエラー\n - ' + e); showError(true, '保育・教育日数マスタのデータ読み取りエラー\n - ' + e);
@@ -124,16 +128,23 @@ class ExtractHandler {
fields: ['担任'], fields: ['担任'],
condition: `学年 in ("${term}")` condition: `学年 in ("${term}")`
}); });
return data && data[0] && data[0]['担任']?.value.map((x)=>x.name).join('、') || ''; return data && data[0] && data[0]['担任']?.value.map((x) => x.name).join('、') || '';
} catch (e) { } catch (e) {
showError(true, '担任マスタのデータ読み取りエラー\n - ' + 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 }) => { writeExcel = ({ records, recordMap, childMaster, dayMaster, termTeacher }, term, { era, year, westernYear, month }) => {
const teachDays = Number(dayMaster['教育日数' + month].value); const teachDays = Number(dayMaster['教育日数' + month].value);
const careDays = Number(dayMaster['保育日数' + month].value); const careDays = Number(dayMaster['保育日数' + month].value);
const holidaySet = this.toHolidaySet(dayMaster['休日_' + month + '月'].value);
return async (api, worksheet) => { return async (api, worksheet) => {
const baseCells = findCellsInfo(worksheet, ['13', '16', '19', '25', '(担 任)', '1', '番号']); const baseCells = findCellsInfo(worksheet, ['13', '16', '19', '25', '(担 任)', '1', '番号']);
@@ -216,6 +227,7 @@ class ExtractHandler {
'出席停止': 0, '出席停止': 0,
'病欠': 0, '病欠': 0,
'自欠': 0, '自欠': 0,
'休日欠席': 0
} }
// 日 // 日
recordWrapper.list.forEach((record, i) => { recordWrapper.list.forEach((record, i) => {
@@ -224,10 +236,18 @@ class ExtractHandler {
if (res === '出席') { if (res === '出席') {
return; return;
} }
if (res === '出席停止' && record["出席停止理由"].value) { if (res === '出席停止') {
reasons.push(record["出席停止理由"].value); if (record["出席停止理由"].value) {
reasons.push(record["出席停止理由"].value);
}
updateCell(row, { base, right: 2 + i }, '×');
return
} }
updateCell(row, { base, right: 2 + i }, res === '出席停止' ? '×' : ''); // 欠席
if (holidaySet.has(record['登園日'].value)) {
sum['休日欠席']++;
}
updateCell(row, { base, right: 2 + i }, '');
}) })
// 出 席 // 出 席
@@ -238,7 +258,7 @@ class ExtractHandler {
updateCell(row, { base, right: 36 }, sum['自欠']); updateCell(row, { base, right: 36 }, sum['自欠']);
// 教育日数 // 教育日数
// updateCell(row, { base, right: 37 }, sum['出席'] + sum['出席停止'] - sum['病欠'] - sum['自欠']); // updateCell(row, { base, right: 37 }, sum['出席'] + sum['出席停止'] - sum['病欠'] - sum['自欠']);
updateCell(row, { base, right: 37 }, teachDays - sum['病欠'] - sum['自欠']); updateCell(row, { base, right: 37 }, teachDays - sum['病欠'] - sum['自欠'] + sum['休日欠席']);
// 備考 // 備考
updateCell(row, { base, right: 38 }, reasons.join("\n")); updateCell(row, { base, right: 38 }, reasons.join("\n"));

View File

@@ -1,57 +0,0 @@
(function () {
"use strict";
// ------------------- 詳細画面表示時の処理 -------------------
kintone.events.on('mobile.app.record.detail.show', function (event) {
const area = kintone.mobile.app.record.getSpaceElement('header-clocking-btn-area');
const clockIn = createBtn('clock-in', '登園', dateToFieldInDetail('登園時刻'));
const clockOut = createBtn('clock-out', '帰園', dateToFieldInDetail('帰園時刻'));
area.appendChild(clockIn);
area.appendChild(clockOut);
hideSpaceField(['clock-in-btn-area', 'clock-out-btn-area']);
return event;
});
function dateToFieldInDetail(fieldCode) {
return async function (e) {
await new KintoneRestAPIClient().record.updateRecord({
app: kintone.mobile.app.getId(),
id: kintone.mobile.app.record.getId(),
record: {
[fieldCode]: {
value: getCurrentTime()
}
}
});
location.reload();
}
}
// ------------------- 編集画面表示時の処理 -------------------
kintone.events.on(['mobile.app.record.create.show', 'mobile.app.record.edit.show'], function (event) {
const clockIn = createBtn('clock-in', '登園', dateToFieldInEdit('登園時刻'));
kintone.mobile.app.record.getSpaceElement('clock-in-btn-area').appendChild(clockIn);
const clockOut = createBtn('clock-out', '帰園', dateToFieldInEdit('帰園時刻'));
kintone.mobile.app.record.getSpaceElement('clock-out-btn-area').appendChild(clockOut);
return event;
});
function dateToFieldInEdit(fieldCode) {
return function (e) {
var record = kintone.mobile.app.record.get();
record['record'][fieldCode]['value'] = getCurrentTime();
kintone.mobile.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

@@ -1,21 +0,0 @@
(function () {
"use strict";
addApproveFlowAction(true);
kintone.events.on("mobile.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);
});
})();

File diff suppressed because one or more lines are too long

2433
src/園児台帳/kuc.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -3,22 +3,28 @@
"use strict"; "use strict";
const FIELD_CODE = "ユニークキー"; const FIELD_CODE = "ユニークキー";
// ------------------- 詳細/印刷/編集画面表示時の処理 ------------------- // ------------------- 画面表示時の処理 -------------------
// 詳細画面/印刷画面表示時の処理
kintone.events.on(['app.record.detail.show', 'app.record.print.show'], function (event) { kintone.events.on(['app.record.detail.show', 'app.record.print.show'], function (event) {
hideField(FIELD_CODE); hideField(FIELD_CODE);
return event; return event;
}); });
// ------------------- 編集画面表示時の処理 ------------------- // 編集画面/新規作成画面表示時の処理
kintone.events.on(['app.record.edit.show', 'app.record.create.show'], function (event) { kintone.events.on(['app.record.edit.show', 'app.record.create.show'], function (event) {
const targetFieldEl = kintone.app.record.getSpaceElement("before-unique-key").parentElement.nextSibling; if (event.type === "app.record.create.show") {
targetFieldEl.querySelector('.input-constraints-cybozu').style.display = 'none'; // レコード複製作成時には既存のユニークキーを削除する
event.record[FIELD_CODE].value = "";
event.record[FIELD_CODE]['value'] = "<自動計算:出席番号+学年+クラス+名前>"; }
disableField(event.record, FIELD_CODE); hideField(FIELD_CODE);
return event; return event;
}); });
// 一覧画面での編集モード表示時の処理
kintone.events.on(['app.record.index.edit.show'], (event) => {
disableField(event.record, FIELD_CODE);
return event
})
function hideField(fieldCode) { function hideField(fieldCode) {
kintone.app.record.setFieldShown(fieldCode, false); kintone.app.record.setFieldShown(fieldCode, false);
@@ -28,14 +34,117 @@
record[fieldCode]['disabled'] = true; record[fieldCode]['disabled'] = true;
} }
// ------------------- 編集画面保存時の処理 ------------------- // ------------------- 新規保存時の処理 -------------------
kintone.events.on(['app.record.create.submit', 'app.record.edit.submit', 'app.record.index.edit.submit'], function (event) { kintone.events.on(['app.record.create.submit.success'], async function (event) {
event.record[FIELD_CODE]['value'] = getUniqueKey(event.record); // 新規作成したレコードのIDをユニークキーとして設定
const api = new KintoneRestAPIClient();
await updateUniqueKey(api, event);
return event; return event;
}); });
function getUniqueKey(record) { // ------------------- 編集保存時の処理 -------------------
return (record['出席番号']['value'] + '_' + record['学年']['value'] + '_' + record['クラス']['value'] + '_' + record['名前']['value']).substring(0, 64); // 関連アプリ情報の定義
// uniqueKeyField 園児台帳を参照するフィールドコード
// dateField 日付範囲指定用フィールド
const LOOKUP_APPS_INFO = {
"園児別出欠簿入力": {
uniqueKeyField: "園児ユニークキー",
dateField: "登園日"
}
}
async function updateUniqueKey(api, event) {
await api.record.updateRecord({
app: env["園児台帳"].appId,
id: event.recordId,
record: {
[FIELD_CODE]: {
value: event.recordId
}
}
});
}
// 保存成功後イベント処理(編集/一覧編集)
kintone.events.on([
'app.record.edit.submit.success',
'app.record.index.edit.submit.success'
], async (event) => {
loading(true, 'データを反映中...');
const api = new KintoneRestAPIClient();
const uniqueKeyValue = event.record[FIELD_CODE]['value'];
const yearRange = getYearRange()
// レガシーキー判別(旧方式: 出席番号+学年+クラス+名前)
const isLegacyUniqueKey = (event.recordId + "") !== uniqueKeyValue
let newKeyGetter;
if (isLegacyUniqueKey) {
// 旧キー方式から移行する処理レコードIDを使用するように変更し、関連する全ての参照箇所を更新する必要があります
await updateUniqueKey(api, event);
newKeyGetter = () => event.recordId
}
for (const appName of Object.keys(LOOKUP_APPS_INFO)) {
// 対象年度の関連レコードを取得
const records = await getAllUpdatedRecords(api, appName, uniqueKeyValue, yearRange, newKeyGetter)
if (!records || records.length === 0) {
continue;
}
try {
const updateResult = await api.record.updateAllRecords({
app: env[appName].appId,
records
});
updateResult.yearRange = yearRange;
showSuccessDialog(updateResult)
} catch (e) {
showError(true, `アプリ「${appName}」のデータ上書処理中にエラーが発生しました\n - ` + e);
}
}
loading(false)
return event;
});
async function getAllUpdatedRecords(api, appName, uniqueKeyValue, yearRange, newKeyGetter) {
const uniqueKeyField = LOOKUP_APPS_INFO[appName].uniqueKeyField
const dateField = LOOKUP_APPS_INFO[appName].dateField
newKeyGetter = newKeyGetter || (record => record[uniqueKeyField].value)
try {
const records = await api.record.getAllRecordsWithId({
app: env[appName].appId,
condition: `${uniqueKeyField} = "${uniqueKeyValue}" and ${dateField} >= "${yearRange.start}" and ${dateField} <= "${yearRange.end}"`
});
return records.map((record) => {
return {
id: record.$id.value,
record: {
[uniqueKeyField]: {
value: newKeyGetter(record)
}
}
};
});
} catch (e) {
showError(true, `アプリ「${appName}」のデータ上書のためにデータを取得中にエラーが発生しました\n - ` + e);
}
}
function showSuccessDialog(updateResult) {
const contentEl = document.createElement('div');
contentEl.style.fontSize = '16px';
contentEl.innerHTML = `${updateResult.yearRange.start.split("-")[0]}年度の${updateResult.records.length}件のデータを更新しました。`;
showDialog({
title: '更新が完了しました',
content: contentEl,
cancel: false
});
} }
})(); })();

View File

@@ -1,41 +0,0 @@
(function () {
"use strict";
const FIELD_CODE = "ユニークキー";
// ------------------- 詳細/印刷/編集画面表示時の処理 -------------------
kintone.events.on(['mobile.app.record.detail.show'], function (event) {
hideField(FIELD_CODE);
return event;
});
// ------------------- 編集画面表示時の処理 -------------------
kintone.events.on(['mobile.app.record.edit.show', 'mobile.app.record.create.show'], function (event) {
const targetFieldEl = kintone.mobile.app.record.getSpaceElement("before-unique-key").nextSibling;
targetFieldEl.querySelector('.control-constraints-gaia').style.display = 'none';
event.record[FIELD_CODE]['value'] = "<自動計算:出席番号+学年+クラス+名前>";
disableField(event.record, FIELD_CODE);
return event;
});
function hideField(fieldCode) {
kintone.mobile.app.record.setFieldShown(fieldCode, false);
}
function disableField(record, fieldCode) {
record[fieldCode]['disabled'] = true;
}
// ------------------- 編集画面保存時の処理 -------------------
kintone.events.on(['mobile.app.record.create.submit', 'mobile.app.record.edit.submit'], function (event) {
event.record[FIELD_CODE]['value'] = getUniqueKey(event.record);
return event;
});
function getUniqueKey(record) {
return (record['出席番号']['value'] + '_' + record['学年']['value'] + '_' + record['クラス']['value'] + '_' + record['名前']['value']).substring(0, 64);
}
})();

View File

@@ -1,12 +0,0 @@
(function () {
"use strict";
kintone.events.on("mobile.app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
})();

View File

@@ -1,12 +0,0 @@
(function () {
"use strict";
kintone.events.on("mobile.app.record.detail.process.proceed", (event) => {
const field = statusFieldMap[event.nextStatus.value];
if (field) {
event.record[field].value = kintone.getLoginUser().name;
}
return event;
});
})();

View File

@@ -0,0 +1,74 @@
# 不要ファイル削除のお願い
以下の各アプリのカスタマイズ画面から、スマートフォン用のJavaScript / CSSファイルを削除してください。
## 2.保育計画 月案0歳児用
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=77](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=77)
## 3.保育計画 月案
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=17](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=17)
## 4.保育計画 週案
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=18](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=18)
## 7.園児台帳
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=16](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=16)
## 8.学期反省・評価
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=49](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=49)
---
# 更新ファイルアップロードのお願い
以下の各アプリのカスタマイズ画面から、該当ファイルをアップロードしてください。
## 1.園児別出欠簿入力
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=19](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=19)
**更新ファイル**:
- `utils.js`
- `ExtractHandler.js`
- `detail-page-desktop.js`
## 6.個別配慮
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=23](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=23)
**更新ファイル**:
- `main.js`
## 7.園児台帳
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=16](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=16)
**更新ファイル**:
- `env.js`
- `KintoneRestAPIClient.min.js`
- `kuc.min.js`
- `utils.js`
- `main.js`
## 保育・教育日数マスタ
**管理画面URL**: [https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=41](https://sagamikouseikai.cybozu.com/k/admin/app/customize?app=41)
**更新ファイル**:
- `main.js`
- `main.css`
**フォーム更新**:
- スペース追加「calendar-area」
- グループ追加:「休日の生データ」(以下の月別テキストフィールドを含む):
1. 「休日_4月」
2. 「休日_5月」
3. 「休日_6月」
4. 「休日_7月」
5. 「休日_8月」
6. 「休日_9月」
7. 「休日_10月」
8. 「休日_11月」
9. 「休日_12月」
10. 「休日_1月」
11. 「休日_2月」
12. 「休日_3月」
![フォーム.png](./フォーム.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB