지난 포스팅에서 정리해 둔 고려사항들 중, 근무와 인원에 대한 내용만 가져와봤다.
- 대대 -> 중대 -> 소대 순으로 병력들의 소속이 존재한다.
- 각 분대별 인원의 수가 유동적이다.
- 근무의 종류가 다양하다.
- 몇몇 근무는 주간과 야간이 구분된다.
- 한 타임의 근무마다 필요한 인원도, 타임도 근무마다 다르다.
이번 프로젝트의 핵심은 "근무"와 "인원"의 구조화라고 생각한다.
근무는 work, 인원은 user라고 변수 네이밍을 하고, 좀 더 구체화된 설계를 진행해야한다.
이해하기 쉽게 요즘 공부하고있는 TypeScript로 구조를 그려보자면...
interface Work {
workId: number;
assignUsers: number[]; // 할당된 userId 리스트
userNumber: number; // 이 근무에 필요한 user 수
timeStart: number; // ex) 1번초부터 시작
timeEnd: number; // ex) 3번초까지 있는 근무
isDayType: boolean; // dayType으로 계산하는지 여부. 밑에서 추가설명
dayTypeList?: number[];// 날짜의 종류로 계산한다면, 할당되는 날 종류
dayList?: number[]; // 요일로 계산한다면, 할당되는 요일 종류
}
interface Day {
workList: Work[]
dayType: 1 | 2 | 3 | 4 | 5; // 1: 평일, 2: 평일 전날, ...
day: number; // 0: 일 ~ 6: 토
}
interface Calendar: Day[]
day
와 dayType
두가지로 구분될 필요가 있던 이유는, 우리 부대의 특징 때문이었다.
요일별로 구분하는 근무가 있었고,
휴일/평일별로 구분하는 근무가 있었기 때문이다.
요일별로 구분할때는 공휴일이 무시되겠지만, 후자는 공휴일과 휴일 전날 근무를 구분해주어야했다.
그렇기에 IsDayType
플래그로 근무의 날짜 산정 방식을 계산해서, 그에 맞는 데이터로 근무를 계산했다.
인원 정보도 마찬가지로 TypeScript로 표현하자면...
interface User {
id: number;
name: string;
workData: number[]; // 근무를 선 횟수들을 저장하는 리스트
workDay: number; // 근무를 선 횟수
canDay: number; // 근무를 설 수 있던 모든 횟수 (입대일)
groupId: number; // 아래 구분들은 밑에서 설명
teamId: number;
}
근무를 선 횟수와 근무를 설 수 있던 모든 횟수를 저장한다.
내가 생각한 이번 개발의 핵심이다.
당직은 근무자를 인수인계하면서 이전 근무자의 데이터를 전부 받아갔지만,
일반적인 근무는 다르다.
내가 근무를 100번 서왔다고 해서, 공평성을 위해 새로 들어온 신병이 100번 서야 하는가?
당연히 틀린 말이다.
내가 생각한 해결책은 모든 인원의 근무 비율을 계산해서, 최적의 근무자를 선정하는 것이다.
function getWorkRate(userId){
const { workDay, canDay } = userList[userId];
return workDay / canDay;
}
근무일 100일중 30일간 근무를 선 A일병과,
근무일 200일중 70일간 근무를 선 B상병이 있다면, 다음 근무에는 A일병이 우선적으로 들어가는게 옳다.
근무일은 동시에 1씩 증가하고, 근무를 선 사람과 서지 않은 사람과의 근무 비율의 차이는 줄어들게 된다.
다음은 인원을 묶는 단위인 "그룹" 이다.
interface Group {
groupId: number;
name: string;
userList: number[]; // 그룹에 속한 유저id의 배열
workList: number[]; // 이 그룹이 할당받은 근무의 목록
}
그룹은 중대 / 소대 등의 단위가 아니다.
같은 근무를 서고 있는 사람끼리의 단위다.
같은 중대라고 모두가 같은 근무를 서는 것은 아니다.
그룹은 특정한 상황에도 활용할 수 있다.
환자들은 위병소 근무를 서지 않아. 대신 불침번을 자주 들어가지.
코로나 격리자들은 야간에 밖에서 하는 근무인 탄약고 근무만 설 수 있어.
그룹에 인원들을 담고, 그룹에 근무를 할당시킨다.
여기에 소속을 포함하기 위한 Team이라는 단위도 있다.
그룹과 똑같게 유저를 묶어두지만, 이는 순전히 UX를 위한 기능이며, 근무의 매칭에는 관여하지 않는다.
interface Team {
id: number;
name: string;
uesrList: number[] // 소속에 포함된 UserId의 배열
}
Team으로 모아 화면을 구성할 것이다.
같은 중대/소대 를 Team으로 모아 보여주면, 가독성에 도움될 것이다. (자신의 이름을 찾는 것도 일이니)
이렇게 핵심 로직의 구성과, UX를 신경쓴 데이터 설계를 해보았다. 적어도 한달은 족히 걸린 것 같고, 조건이 틀리지 않았는지 타 중대 행보관님들께 검증받으며 계산했다.
아직은 생각나는 예외사항이 없고, 행보관님들은 "뭐 만들어 봐라 ㅋㅋ" 정도로만 생각하셨으니, 진짜 만들어 보여드려야겠다. 후