창고에서 상자 바코드를 찍고 ‘삑’ 소리가 났다고 입고가 끝난 것은 아닙니다. 기계가 문자열을 읽은 것과 올바른 상품을 올바른 작업에 정확한 수량으로 기록한 것은 다른 일입니다. 이 사이를 생략하면 중복 스캔, 다른 발주 건에 붙은 상품, 늦게 동기화된 오프라인 기록 때문에 재고가 조금씩 틀어집니다.
스캔을 업무 사건으로 바꾼다
현장 앱은 읽은 코드의 형식부터 확인해야 합니다. 사용 중인 심벌과 식별자 규칙에 맞는지, 해당 코드가 상품·박스·로트·위치 중 무엇을 뜻하는지 구분합니다. 다음에는 현재 작업 맥락을 검사합니다. 이 입고 지시에 포함된 상품인가, 이미 취소된 발주인가, 유통기한이나 로트가 필요한 품목인가, 수량 단위가 맞는가? 여러 검증을 통과한 뒤에야 ‘재고 변경 요청’이라는 사건을 만듭니다.
단순히 코드 문자열을 재고 테이블에 바로 더하는 방식은 편해 보이지만, 나중에 왜 숫자가 달라졌는지 설명하기 어렵습니다. 스캔 원문, 작업자, 기기, 작업 지시, 위치, 수량, 시각, 처리 결과를 별도 이벤트로 남기면 오류를 추적할 수 있습니다. 개인 정보와 보존 기간은 목적에 맞게 제한합니다.

두 번 찍었을 때 무엇이 일어나야 하는가
같은 바코드를 두 번 읽는 행위가 언제나 오류는 아닙니다. 낱개 두 개를 각각 스캔했다면 수량 2가 맞을 수 있고, 상자 하나를 실수로 다시 찍었다면 중복입니다. 이를 구분하려면 업무 단위를 모델에 넣어야 합니다. 고유 시리얼이 있는 물품은 동일 시리얼의 중복 확정을 막을 수 있습니다. 동일 상품을 여러 개 받는 경우에는 지시 수량과 현재 누적 수량을 화면에 보여주고, 초과분을 바로 확정하지 않는 편이 안전합니다.
네트워크 타임아웃이 발생했을 때 사용자가 재시도할 수도 있습니다. 이 경우 앱이 처음 만든 scan_event_id를 유지해 같은 요청을 다시 보내야 합니다. 서버는 이 ID가 이미 처리됐으면 이전 결과를 돌려줍니다. 이것이 ‘재전송은 가능하지만 재고는 한 번만 바뀌는’ 구조입니다. 버튼을 잠시 비활성화하는 것만으로는 기기 재시작이나 네트워크 재전송을 막을 수 없습니다.
서버 쪽에서는 이벤트 ID의 중복 방지를 데이터베이스 제약으로도 보강하는 편이 좋습니다. 두 기기가 거의 동시에 같은 요청을 보내면 화면의 검사만으로는 경쟁 조건을 막기 어렵기 때문입니다. 재고 수량 변경과 처리 완료 기록은 같은 트랜잭션 안에서 확정하고, 실패하면 함께 되돌립니다. 이렇게 해야 담당자가 다시 눌렀을 때 서버가 이미 처리된 결과인지, 아직 처리되지 않은 요청인지 명확히 답할 수 있습니다.
오프라인을 허용한다면 충돌 정책도 필요하다
현장 무선망이 끊겨도 스캔을 계속 받아야 한다면, 앱은 확정 전 이벤트를 기기에 안전하게 보관하고 ‘동기화 대기’를 명확히 표시합니다. 온라인 복귀 후 순서대로 전송하면서 서버 검증을 다시 받습니다. 오프라인에서 정상으로 보였던 상품이 그 사이 지시에서 제외됐을 수 있습니다. 이런 충돌은 조용히 버리거나 강제 반영하지 말고 작업자 검토 대상으로 분리합니다.

화면의 피드백도 업무 상태를 구분해야 합니다. 스캐너가 읽었을 때의 소리와 서버가 입고를 확정했을 때의 표시를 다르게 두면 오해가 줄어듭니다. 동기화 대기·확정·검토 필요·중복 처리의 상태가 잘 보이도록 하세요. 장갑 착용과 소음이 있는 현장에서는 색만이 아니라 문구와 진동 같은 보조 신호도 함께 고려합니다.
현장 도입 전 시험할 상황
① 같은 코드를 연속 두 번 스캔 ② 다른 발주에 속한 상품 스캔 ③ 지시 수량 초과 ④ 오프라인에서 여러 건 스캔 후 재접속 ⑤ 서버는 반영했으나 기기는 타임아웃 ⑥ 같은 작업을 두 기기에서 동시에 수행. 이 상황을 통과해야 ‘인식률이 높다’는 평가가 실제 재고 정확도로 이어집니다. 자동화의 목표는 빠르게 찍는 것이 아니라 올바른 사건만 확정하는 것입니다.
참고: GS1 바코드 식별 방식 안내.