← 모든 프로젝트

약국 출퇴근·급여 시스템

QR과 GPS 출근 체크, 한국 급여 규칙, 그리고 실제 직원과 실제 폰에서만 나오는 버그들

기간
2026년 4월 – 2026년 9월
대상
교대 근무 직원이 있는 약국, 여러 사업장을 다룰 수 있게 설계
역할
1인 개발: 설계, 개발, 배포, 운영
기술
next.js, typescript, prisma, postgresql, pwa, web push, windows server
규모
커밋 168개, 데이터 모델 62개, 마이그레이션 34개, 타입스크립트 약 3.1만 줄
외주 작업, neoworks

다른 언어: english

어떤 시스템인지

직원이 근무지에 있는 QR 코드를 찍으면, 폰이 실제로 그 자리에 있는지 확인하고 그게 출퇴근 기록이 돼요. 그 기록으로 급여를 계산하고요. 여기에 근무 요청과 승인, 공유 캘린더, 할 일, 문서, 서명까지 받는 근로계약서가 붙었고, 나중에는 고객용 상담 키오스크도 생겼어요. PWA라서 직원들은 브라우저에서 바로 설치하고 푸시 알림을 받아요.

처음 돌아가는 버전은 4월에 하루 만에 올렸어요. 그 뒤로 사람들이 쓰면서 이것저것 알려주는 대로 9월까지 계속 커졌어요.

급여 계산은 대부분 규칙이에요

급여 계산 함수 자체는 250줄이 안 돼요. 오래 걸린 건 그 안에 들어가는 규칙이었어요. 한국 노동법은 시급 기준인데, 휴게시간은 근무 길이에 따라 빠지고(4시간 넘으면, 8시간 넘으면), 연장근로는 일 단위와 주 단위로 같은 시간을 두 번 세지 않게 계산해야 하고, 야간 근로에는 가산이 붙고, 공휴일 근로는 처음 8시간과 그 이후 요율이 다르고, 주휴수당은 그 주에 시간도 일수도 충분히 채워야만 나와요. 이게 전부 설정값이에요.

실제로 있었던 버그 하나: 일부 직원은 개인별 휴게 규칙이 따로 있었어요. 그런데 그 칸이 비어 있으면 코드가 전체 기본값인 60분으로 넘어가서, "휴게 없음"으로 해둔 사람도 한 시간씩 깎이고 있었어요. 지금은 직원별 설정이 적힌 그대로 적용되고, 비어 있으면 0이에요.

GPS는 항상 조금씩 틀려요

출근하려면 유효한 사업장 QR 토큰, 사업장 반경 안의 GPS 값, 그리고 충분히 괜찮은 정확도가 필요해요. 이 부분은 문제가 없었어요. 문제는 자동 퇴근이었어요.

클라이언트가 출근과 퇴근이 동시에 찍힌다고 알려왔어요. 기록을 보니 반경 60m 사업장 중심에서 16.3m 떨어진 곳에서 출근했고, 168초 뒤에 폰이 61.6m라고 보고해서 시스템이 퇴근 처리를 했어요. 경계선에서 1.6m 벗어난 건데, 실내 GPS 오차 안에 충분히 들어가는 거리예요. 원인은 자동 퇴근이 수동 출퇴근과 같은 정확도 기준을 쓰고 있었다는 거예요. 60m 사업장이면 180m까지 틀린 값도 받아주는 기준이었어요.

그래서 자동 퇴근에는 더 엄격한 기준을 따로 뒀어요. 정확도가 60m 이내여야 하고, 오차 원에서 가장 가까운 쪽 끝조차 반경 밖으로 30m는 벗어나 있어야 해요. 출근 후 처음 10분 동안은 아무것도 하지 않고요. 사람이 버튼을 누르는 건 느슨한 기준이어도 괜찮지만, 시스템이 알아서 누군가의 근무를 끝내는 건 그러면 안 돼요.

한 번도 안 돌았던 알림

근무 알림과 결근 알림은 15분마다 도는 예약 작업에서 실행됐어요. 매번 성공했다고 나왔어요. 그런데 2주치 데이터를 재보니 하루에 활성 스케줄이 78개 정도 있었는데 저장된 알림은 0개였어요. 스케줄을 다시 돌려보니 원래 324개가 나갔어야 했어요.

원인은 이랬어요. 서버가 Windows이고, 작업이 PowerShell 5.1로 돌았는데 이게 기본으로 TLS 1.0을 쓰고, 리버스 프록시는 TLS 1.2 이상만 받아요. 모든 호출이 실패했는데 빈 catch {}가 그걸 숨기고 있었어요. 지오펜스 점검도 같은 이유로 죽어 있었고요. 그래서 스크립트를 저장소 안으로 옮기고, 앱을 loopback으로 호출하게 하고, 실패하면 0이 아닌 코드로 종료하면서 로그를 남기게 바꿨어요.

그 밑에 버그가 하나 더 있었어요. 알림은 예정 시각 14분 안에서만 보낼 수 있었는데 점검은 15분마다였어요. 한 번만 늦게 돌아도 그날 알림은 사라졌어요. 23:55에 끝나는 근무는 아예 알림을 받을 수가 없었고요.

연속 터치

어떤 직원의 수동 입력이 7.9초 동안 5번 들어갔고, 다른 건 0.4초 사이에 두 번 들어갔어요. 화면이 반응이 없으니까 계속 누른 거예요. 제출하는 동안 버튼을 잠갔지만, 이미 날아간 요청까지 막을 수는 없어요. 코드에서 중복을 확인하는 것도 마찬가지예요. 로컬 테스트에서 동시 요청 5개가 전부 확인을 통과하고 5줄을 썼어요. 결국 해결한 건 데이터베이스의 unique 제약이었고, 그 앞에 코드 확인을 둬서 조용히 실패하게 했어요. 마이그레이션을 돌리기 전에는 기존 중복부터 정리해야 했는데, 둘 중 한쪽에만 있던 메모가 날아가지 않게 해야 했어요.

뒤로 가기 버튼도 같은 모양의 버그가 있었어요. 스캔 페이지로 돌아가서 다시 누르면 출근과 퇴근이 번갈아 찍혔어요. 지금은 출퇴근을 찍을 때마다 서버에서 3분 동안 잠가요.

키오스크와 프린터

키오스크는 QR을 띄워요. 고객이 자기 폰으로 찍고, 동의하고, 계정 없이 한 번 인증해요. 접근 권한을 주는 건 URL에 있는 토큰 하나뿐이라서, 32바이트 랜덤 값에 10분 동안만 유효하고 한 번 쓰면 폐기돼요. 건강 정보 조회는 원래 고객 폰으로 승인 요청을 두 번 보냈어요. 두 요청이 세션 ID 하나를 같이 쓰게 해서 한 번으로 줄였어요.

출력은 매장 PC가 서버에서 작업을 가져오는 방식이라 공유기 포트를 열 필요가 없어요. 30분 넘은 작업은 만료돼요. 하루 종일 꺼져 있던 PC가 다음 날 아침에 모르는 사람 스무 명의 건강 요약을 출력하면 안 되니까요.

지금은

매일 쓰고 있고, 9월 기준으로 직원 명단에 44명이 있어요. Windows 서버에서 서비스로 돌아가요. 마지막으로 한 작업은 키오스크 출력 대기열이었어요.