업무 시스템 · 2024
바코드 스캔 출고 관리 프로그램
발주 엑셀을 올려 두고 상품 바코드를 찍으면 출고 수량이 차감되고 이력이 남는 데스크톱 앱
발주 엑셀을 그대로 올려 상품별 출고 잔여수량을 관리하고, 작업대에서 바코드를 찍을 때마다 수량이 차감되고 출고 일시가 기록되도록 만든 윈도우 데스크톱 프로그램입니다.
- 고객
- 비공개 고객
- 분야
- 업무 시스템

개요
발주 건에 맞춰 상품을 박스에 담아 내보내는 출고 작업을 처리하는 윈도우 데스크톱 프로그램입니다. 발주 내역이 담긴 엑셀을 올려 두고, 작업대에서 상품 바코드를 찍으면 해당 발주 건의 출고 잔여수량이 하나씩 줄어듭니다.
작업자는 조회 조건으로 그날 처리할 발주 범위를 좁힌 다음 출고 처리 모드로 전환하고, 그 뒤로는 화면을 조작하지 않고 상품만 찍습니다. 찍을 때마다 큰 글씨의 안내가 1초 동안 떠서 처리 결과와 남은 수량을 알리므로, 상품에서 눈을 떼지 않고도 진행 상황을 확인할 수 있습니다.
데이터는 SQLite 파일 하나에 들어갑니다. 서버를 두지 않고 파일을 주고받는 것만으로 작업 PC를 옮기거나 백업할 수 있습니다.

과제
발주 내역은 엑셀로 들어옵니다. 한 파일 안에 여러 거래처, 물류센터, 입고예정일의 발주 건이 섞여 있고, 같은 상품이 서로 다른 발주번호에 여러 줄로 나뉘어 들어 있습니다. 바코드만으로는 어느 줄의 수량을 깎아야 하는지 정해지지 않으므로, 상품을 찍었을 때 차감할 건을 고르는 규칙이 필요했습니다.
작업대 환경도 제약이었습니다. 별도 서버 없이 PC 한 대에서 돌아가야 했고, 바코드 스캐너는 시리얼 포트로 값을 흘려보내는 모델을 씁니다. 출고 중에는 양손에 상품이 들려 있으므로, 스캔 외의 조작을 요구하지 않아야 했습니다.
해결 방법
발주 엑셀 업로드
- 거래처명, 발주번호, 물류센터, 입고예정일시, 상품명, 바코드, 업체납품가능수량 열을 읽습니다. 잔여납품수량과 메모 열은 있으면 함께 읽습니다
- 엑셀 한 건이 이름을 가진 테이블 하나가 되며, 이미 쓴 이름은 다시 쓸 수 없습니다
- 필수 열이 빠져 있으면 어느 열인지 알려 주고, 값의 형식이 맞지 않는 행은 건너뜁니다
조회와 출고
- 테이블 이름, 거래처명, 물류센터, 상품명, 바코드, 발주번호를 부분 일치로 좁히고, 입고예정일은 기간으로 지정합니다. 기본 기간은 최근 1년입니다
- 출고 처리 모드를 켜면 스캐너가 연결되고, 조회된 목록 안에 있는 상품만 스캔이 처리됩니다
- 같은 바코드가 여러 발주 건에 걸쳐 있으면 입고예정일이 빠른 건부터 차감합니다
- 잔여수량이 0이 된 줄은 배경색이 바뀌어 목록에서 바로 구분됩니다
- 스캔으로 처리하기 어려운 건은 수량을 직접 넣어 출고합니다. 음수를 넣으면 잘못 처리한 수량을 되돌릴 수 있습니다

그 밖의 기능
- 박스 준비: 조회된 내역을 발주번호 단위로 합쳐 거래처, 물류센터, 입고예정일, 총수량을 보여 줍니다
- 발주번호 락: 내보내면 안 되는 발주번호를 걸어 두면, 스캔해도 처리되지 않고 잠김 표시만 뜹니다
- 엑셀 다운로드: 조회 결과를 테두리와 정렬 서식이 들어간 엑셀 파일로 내려받습니다
- 메모: 목록에서 상품 줄을 더블클릭해 메모를 남기고, 이 메모는 엑셀에도 함께 나갑니다

시스템 구조
구성
- UI: wxPython. 조회 패널, 발주 목록, 선택한 상품의 정보와 출고 기록 패널로 나뉩니다
- 저장소: SQLite 파일 한 개. 발주 내역을 담는
contents, 출고 이력을 담는logs, 업로드 단위를 담는table_names세 테이블입니다 - 엑셀 입출력: openpyxl
- 스캐너: pyserial. 설정한 COM 포트와 통신 속도로 열고, 전용 스레드가 줄바꿈 문자가 올 때까지 읽어 한 건의 바코드로 넘깁니다
데이터 흐름
발주 엑셀을 올리면 테이블 이름과 함께 contents에 들어갑니다. 조회를 누르면 조건에 맞는 행을 입고예정일 순으로 읽어 화면 목록과 메모리 캐시에 함께 싣습니다. 스캔된 바코드는 이 캐시에서 찾으므로, 그날 조회해 둔 범위 밖의 상품은 아예 처리되지 않습니다.
차감이 일어나면 contents의 잔여수량이 줄고 logs에 출고 일시와 수량이 한 줄 쌓입니다. 상품 단위로 기록이 남으므로 어느 건이 언제 나갔는지 나중에 되짚을 수 있습니다.
구현
저장 전 작업본 분리
DB 파일을 열면 임시 폴더에 복사본을 만들고 모든 작업을 복사본에서 합니다. 저장을 눌렀을 때만 원본에 반영되므로, 엑셀을 잘못 올렸거나 스캔을 잘못한 날은 저장하지 않고 끄면 원래 파일이 그대로 남습니다. 긴 작업에서 이를 잊지 않도록 30초 주기의 자동 저장을 선택할 수 있게 했습니다.
불러올 때는 파일을 열기 전에 세 테이블과 각 열이 모두 있는지 확인하고, 빠진 항목을 짚어 알려 줍니다.
스캔 처리
스캐너 읽기는 별도 스레드에서 돌고, 바코드 한 건이 완성되면 wx.CallAfter로 UI 스레드에 넘겨 화면과 DB를 갱신합니다. 처리 중에는 플래그를 세워 뒤따라 들어온 스캔을 무시하므로, 빠르게 연속으로 찍어도 같은 상품이 두 번 차감되지 않습니다.
처리 결과는 색으로 구분합니다. 차감에 성공하면 초록, 잔여수량이 0이 되었거나 이미 출고가 끝난 바코드면 빨강으로 띄우고 1초 뒤 스스로 닫힙니다. 차감된 줄로 목록이 자동으로 스크롤되고 해당 줄이 선택되므로, 화면을 따로 찾아볼 필요가 없습니다.

잘못된 출고 막기
내보내면 안 되는 발주번호를 잠금 목록에 넣어 둘 수 있습니다. 잠긴 발주번호에 속한 상품은 바코드가 맞아도 차감되지 않고 잠김 표시만 뜹니다. 잠금 목록은 설정 파일에 남아 프로그램을 다시 켜도 유지됩니다.
한 번에 너무 많은 내역이 올라와 화면이 멈추지 않도록, 조회 결과가 3천 건을 넘으면 조건을 좁히도록 안내합니다.

결과
발주 엑셀을 올리고 조회 범위만 잡아 두면, 이후 출고 작업은 상품을 찍는 것으로 끝납니다. 수량을 세어 종이에 적거나 엑셀에서 줄을 찾아 고치는 과정이 들어가지 않습니다.
출고가 상품 단위로 기록되므로 발주 건별로 몇 개가 남았는지 화면에서 바로 보이고, 어느 시점에 무엇이 나갔는지 나중에 확인할 수 있습니다. 작업이 끝난 뒤에는 조회 결과를 그대로 엑셀로 내려 공유하거나 보관할 수 있습니다.
설치 과정과 서버가 없고 데이터가 파일 하나에 들어 있어, 작업대 PC에 복사해 두는 것만으로 쓸 수 있습니다.
사용 기술
- Python
- wxPython
- SQLite
- openpyxl
- pyserial
화면




진행하려는 프로젝트가 있으신가요?
해결하려는 문제와 지금 상황을 알려 주시면, 어떤 방식이 맞을지 함께 검토합니다.
프로젝트 문의하기