Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion js/modules/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,8 @@ function mergeShiftsByPerson(shiftsForDate) {
// 個人ごとにグループ化(時間帯とUUIDのマッピングを保持)
const shiftsByPerson = {};
shiftsForDate.forEach(shift => {
const personKey = `${getShiftDisplayName(shift)}_${shift.userEmail || shift.email}_${shift.isSpecial ? 'special' : 'regular'}`;
// 表示名が同じ別ユーザーを混ぜないよう、userId で区別する
const personKey = `${getShiftDisplayName(shift)}_${shift.userId}_${shift.isSpecial ? 'special' : 'regular'}`;
if (!shiftsByPerson[personKey]) {
shiftsByPerson[personKey] = {
person: shift,
Expand Down Expand Up @@ -730,3 +731,10 @@ function setupCalendarBulkDelete() {
}
});
}

// テストから実コードを検証できるようにするエクスポートガード
// (ブラウザでは module が未定義のため no-op になる)
// 方針: docs/refactoring/phase-1-test-foundation.md
if (typeof module !== 'undefined' && module.exports) {
module.exports = { mergeShiftsByPerson };
}
2 changes: 2 additions & 0 deletions js/modules/shiftRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ function displayShiftsOnRequestCalendar(allShifts, allSpecialApps) {
const regularShifts = (allShifts || []).map(shift => ({
shiftDate: shift.date,
timeSlot: shift.time_slot,
userId: shift.user_id,
userName: shift.user_name,
nickname: shift.nickname,
realName: shift.real_name,
Expand All @@ -645,6 +646,7 @@ function displayShiftsOnRequestCalendar(allShifts, allSpecialApps) {
const specialShifts = (allSpecialApps || []).map(app => ({
shiftDate: app.date,
timeSlot: app.time_slot,
userId: app.user_id,
userName: app.user_name,
nickname: app.nickname,
realName: app.real_name,
Expand Down
57 changes: 7 additions & 50 deletions js/modules/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,56 +222,6 @@ function getDefaultCapacity(dayOfWeek) {
}
}

// 管理者用:個人ごとに連続する時間帯をマージする関数
function mergeShiftsByPerson(shiftsForDate) {
// 個人ごとにグループ化(時間帯とUUIDのマッピングを保持)
const shiftsByPerson = {};
shiftsForDate.forEach(shift => {
const personKey = `${getShiftDisplayName(shift)}_${shift.userEmail || shift.email}`;
if (!shiftsByPerson[personKey]) {
shiftsByPerson[personKey] = {
person: shift,
shiftsData: [] // 元のシフトデータを保持
};
}
shiftsByPerson[personKey].shiftsData.push({
timeSlot: shift.timeSlot || shift.time,
uuid: shift.uuid
});
});

// 各個人の時間帯をマージ
const mergedShifts = [];
Object.keys(shiftsByPerson).forEach(personKey => {
const personData = shiftsByPerson[personKey];

// 時間帯だけを抽出してマージ
const timeSlots = personData.shiftsData.map(s => s.timeSlot);
const mergedTimeSlots = mergeConsecutiveTimeSlots(timeSlots);

mergedTimeSlots.forEach(mergedTimeSlot => {
// このマージされた時間帯に対応するUUIDだけを抽出
const correspondingUuids = personData.shiftsData
.filter(s => {
// マージされた時間帯に含まれる元の時間帯かチェック
// 例: 13:00-14:00には13:00-13:30と13:30-14:00が含まれる
const [mergedStart, mergedEnd] = mergedTimeSlot.split('-');
const [slotStart, slotEnd] = s.timeSlot.split('-');
return slotStart >= mergedStart && slotEnd <= mergedEnd;
})
.map(s => s.uuid);

mergedShifts.push({
...personData.person,
timeSlot: mergedTimeSlot,
uuids: correspondingUuids // このマージ時間帯に対応するUUIDのみ
});
});
});

return mergedShifts;
}

// 一括削除アクションバーのHTML文字列を生成する関数
function createBulkActionBarHTML(barId, countId, btnId) {
return `<div id="${barId}" class="bulk-delete-action-bar">
Expand Down Expand Up @@ -308,3 +258,10 @@ function getCurrentUserData() {

return null;
}

// テストから実コードを検証できるようにするエクスポートガード
// (ブラウザでは module が未定義のため no-op になる)
// 方針: docs/refactoring/phase-1-test-foundation.md
if (typeof module !== 'undefined' && module.exports) {
module.exports = { getDisplayName, getShiftDisplayName, mergeConsecutiveTimeSlots };
}
73 changes: 73 additions & 0 deletions test/calendar-merge-same-display-name.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* カレンダーの人物ごとのシフトマージのテスト
*
* 回帰防止: 表示名が同じ別ユーザーが、カレンダー上で 1 人にまとめられていた。
* 人物を区別するキーに使っていた userEmail / email はシフトデータに
* 含まれておらず、実質的に表示名だけで区別していたため。
* 同じ時間帯に入っていると、片方のチェックボックスに両方の UUID が入り、
* 1 人分を削除したつもりでもう 1 人のシフトまで削除されていた。
*
* このテストは実コード(js/modules/calendar.js)を直接 import して検証する。
* 方針: docs/refactoring/phase-1-test-foundation.md
*/

const { getShiftDisplayName, mergeConsecutiveTimeSlots } = require('../js/modules/utils.js');
const { mergeShiftsByPerson } = require('../js/modules/calendar.js');

function shift(userId, userName, timeSlot, uuid, isSpecial = false) {
return { shiftDate: '2026-10-08', userId, userName, timeSlot, uuid, isSpecial };
}

describe('mergeShiftsByPerson: 表示名が同じ別ユーザー', () => {
beforeEach(() => {
global.getShiftDisplayName = getShiftDisplayName;
global.mergeConsecutiveTimeSlots = mergeConsecutiveTimeSlots;
});

afterEach(() => {
delete global.getShiftDisplayName;
delete global.mergeConsecutiveTimeSlots;
});

test('同じ時間帯に入っていても、ユーザーごとに別のエントリになる', () => {
const result = mergeShiftsByPerson([
shift('user-1', '田中', '13:00-13:30', 'uuid-1'),
shift('user-2', '田中', '13:00-13:30', 'uuid-2')
]);

expect(result).toHaveLength(2);
expect(result.find(s => s.userId === 'user-1').uuids).toEqual(['uuid-1']);
expect(result.find(s => s.userId === 'user-2').uuids).toEqual(['uuid-2']);
});

test('連続する時間帯でも、別ユーザーのシフトは 1 つにまとめない', () => {
const result = mergeShiftsByPerson([
shift('user-1', '田中', '13:00-13:30', 'uuid-1'),
shift('user-2', '田中', '13:30-14:00', 'uuid-2')
]);

expect(result.map(s => s.timeSlot).sort()).toEqual(['13:00-13:30', '13:30-14:00']);
expect(result.find(s => s.userId === 'user-1').uuids).toEqual(['uuid-1']);
expect(result.find(s => s.userId === 'user-2').uuids).toEqual(['uuid-2']);
});

test('同じユーザーの連続する時間帯は、これまでどおり 1 つにまとめる', () => {
const result = mergeShiftsByPerson([
shift('user-1', '田中', '13:00-13:30', 'uuid-1'),
shift('user-1', '田中', '13:30-14:00', 'uuid-2')
]);

expect(result).toHaveLength(1);
expect(result[0].timeSlot).toBe('13:00-14:00');
expect(result[0].uuids).toEqual(['uuid-1', 'uuid-2']);
});

test('同じユーザーの通常シフトと特別シフトは、これまでどおり別のエントリになる', () => {
const result = mergeShiftsByPerson([
shift('user-1', '田中', '13:00-13:30', 'uuid-r'),
shift('user-1', '田中', '13:30-14:00', 'uuid-s', true)
]);

expect(result).toHaveLength(2);
});
});
Loading