발주서 엑셀 자동화
개요
업무 효율성을 향한 여정, 그리고 개발자로 변화의 시작
영양사로 근무할 당시, 병원 급식 발주서를 자사 발주 시스템에 맞춰 수작업으로 포맷팅하는 일이 매일 반복되었습니다. 각 파일을 처리하는 데 약 30분이 소요되었고, 매일 반복되는 비효율적인 발주서 작업을 조금이라도 자동화할 수 있는 방법에 대해 고민했습니다.Python과 Pandas를 활용해 자동화 스크립트를 개발한 결과, 작업 시간을 20초로 단축할 수 있었습니다. 이 스크립트는 단순히 개인의 작업 효율성을 높이는 데 그치지 않았으며, EXE 프로그램으로 변환해 다른 동료들도 사용할 수 있도록 배포했습니다. 이 경험은 제 업무 방식에 큰 변화를 가져왔고, 더 나아가 개발자로 전직을 결심하게된 계기가 되었습니다.
1. 비효율적인 수작업 발주서 처리
(1) 주요 문제 파악
비효율적인 발주서 처리 과정과 인수인계의 어려움
비호환 파일
병원에서 제공하는 발주서 엑셀 파일은 자사 발주 시스템과 호환되지 않음반복 작업
인수인계 어려움
높은 피로도와 휴먼 에러
복잡한 요구사항과 수작업으로 실수가 자주 발생되었고, 업무 피로도도 높았음
(2) 스크린샷 예시
-
AS-IS



-
TO-BE

(3) 요구사항 일부 예시
- 일부 품목 분리 발주
- 특정 품목(대파, 오이 등)은 5kg, 10kg 단위로 나누어 발주
- 예시: 대파 13kg 주문 시, 5kg + 5kg + 3kg 단위로 행을 나누어 발주
- 선 입고, 당일 입고 처리
- 입고 날짜에 따라 파일을 분리 생성
- 선입고: “선입고”로 표기된 품목은 입고일보다 하루 일찍 발주
- 당일 입고: “당일 입고”로 표기된 품목은 급식일 당일에 받도록 해당 날짜에 발주
- 발주 금지 품목 : “직송”, “인터넷”, “병원” 등이 포함된 품목은 발주서에서 제외
- 쌀 발주서 생성 : 쌀에 해당하는 품목은 별도의 발주서 파일로 분리
- 일요일 예외 처리 : 일요일인 경우, A열에 “{급식일자} 사용분”을 모든 행에 기입
- 수산물 및 육류 발주
- 수산물과 육류는 “치료”, “환자”, “직원”으로 구분하여 발주
- 나머지 품목은 합계로 1건만 발주
2. 자동화 스크립트 개발
자동화를 통환 효과적인 업무 개선
자동화 스크립트를 개발하면 여러 문제를 해결할 수 있을 것이라 생각하였습니다. 개발 후 실제 업무에 자동화 스크립트를 적용한 결과, 30분 걸리던 작업을 단 20초로 단축 시키며 업무 효율성이 비약적으로 향상되었습니다. 이 프로그램은 동료 영양사에게도 배포되어 팀 전체의 업무 흐름을 개선하는데 기여했습니다. 자동화 덕분에 이전에 빈번하게 발생하던 발주 누락과 오류가 사라졌고, 인수인계 역시 한층 간편 해졌습니다.
(1) 자동화 스크립트 개발
- Python과 Pandas 라이브러리를 사용하여 개발
- 병원 발주서를 자사 발주 시스템에 호환되는 형식으로 자동 변환하는 스크립트 작성
- 이 스크립트는 엑셀 파일을 읽어 필요 없는 데이터를 제거하고, 포맷을 맞춰 자동으로 저장하는 기능 수행
(2) EXE 프로그램 변환
- 회사 컴퓨터에서는 Python 설치가 힘든 환경이었으며, 동료마다 Python 설치 및 실행 방법 알려주기에는 번거로웠음
- 자동화 프로그램 실행하기 위해서는 윈도우 프로그램이 필요했으며, Python 스크립트를
.exe파일로 변환하는 방법을 모색함 - 여러 방법 중 가장 대중적인
PyInstaller를 사용해서 Python 스크립트를 윈도우 실행 파일로 패키징 함 - 동료들이 쉽게 사용할 수 있도록 아이콘도 추가해 봄

(3) 배포
- 이 프로그램을 2주 정도 실제 사용해보면서 버그 수정하고 실제 업로드에도 문제가 없는 것을 확인
- 결과적으로 기존 파일마다 30분 걸리던 작업이 약 20초 정도로 대폭 줄어들면서 업무 효율성이 많이 개선됨
- 다른 발주 담당 영양사들도 사용할 수 있도록 프로그램과 사용 방법을 전달
3. 주요 기능과 로직
발주서 엑셀 자동화 로직 흐름
Excel 파일로 부터 데이터를 읽어와, 여러 조건에 맞춰 데이터를 정리 및 재처리 합니다. 최종적으로 선입고, 당일입고, 그리고 쌀 발주서와 본 발주서를 생성하여 Excel 파일로 저장합니다.스크립트를 통해 병원 급식 발주서를 간단하게 포맷에 맞춰 수정할 수 있으며, 필요한 파일들을 자동으로 생성하여 관리하도록 도와줍니다.
3-1. HospitalOrder 클래스
HospitalOrder클래스는 Excel 파일에서 데이터를 읽고, 발주 항목을 처리한 후, 발주서를 생성함- 주요 함수
start함수: Excel 파일을 읽어오고 데이터 전처리, 조건에 따른 필터링 및 수정 후, 결과를 다시 Excel 파일로 저장하는 핵심 함수process_fish_rows함수: 수산물 항목에 대해 특정 조건을 바탕으로 행을 처리함replicate_row함수: 조건에 맞는 행을 복제하여 발주 항목을 세분화 하는 함수
(1) 데이터 읽기 및 초기 처리
pandas를 사용하여 Excel 파일을 읽고, 특정 조건에 맞춰 필터링하여 필요한 데이터만 남기는 작업 수행input_date와meal_date를 통해 주문 일자와 급식 일자를 출력함- 빈 값 또는 특정 조건(ex. “직송”, “인터넷”, “병원” 등)과 일치하는 데이터는 불필요한 항목이므로 삭제함
df = pd.read_excel(excel_file)
# 주문일자와 급식일자 출력
input_date = df['Unnamed: 5'][4]
meal_date = df['Unnamed: 5'][5]
print(f"입고일: {input_date}")
print(f"급식일: {meal_date}")
df = df.dropna(subset=["Unnamed: 3"])
df = df.replace(np.nan, '', regex=True)
df = df.reset_index(drop=True)
df.index += 1
df = df.loc[:, :'Unnamed: 23'] # 오른쪽 열 삭제
df = df[3:] # 첫 3행 제거
# 행 삭제
df = df[~df['Unnamed: 23'].str.contains("인터넷")]
df = df[~df['Unnamed: 23'].str.contains("병원")]
(2) 발주량 구분 및 통합
발주량은 “치료”, “환자”, “직원” 항목별로 나눠져있으며, 이를 환자열에 통합하여 중복된 데이터를 정리함
- 수산물과 육류 제외
- 입력 받은
fish_start_index,fish_end_index,meat_start_index,meat_end_index를 통해 수산물과 육류 범위를 설정 - 수산물과, 육류 범위 내의 데이터는 따로 처리하기 위해 제외함
- 입력 받은
- 발주 구분 통합
- “치료”, “환자”, “직원” 나눠져있는 발주량을 환자열에 발주량 통합
- 수산물과 육류가 아닌 항목에서, T열 또는 V열에 있는 값을 복사하여 R열 또는 T열에 넣음
or index, row in df.iterrows():
column_index = row["Unnamed: 1"]
if column_index >= fish_start_index and column_index <= fish_end_index and not dried_fish_list[index]:
continue
if column_index >= meat_start_index and column_index <= meat_end_index:
continue
if garlic_list[index]:
continue
df.at[index, 'Unnamed: 12'] = df.at[index, 'Unnamed: 14'] = df.at[index, 'Unnamed: 17'] = ''
if "일요일" in input_date:
df.at[index, 'Unnamed: 17'] = df.at[index, 'Unnamed: 20']
else:
df.at[index, 'Unnamed: 14'] = df.at[index, 'Unnamed: 20']
(3) 행 복제 및 처리
- 특정 항목들에 대해 조건에 맞게 행을 복제하여 발주량을 나누는 작업 수행
- 단위 수량을 넘는 항목은 새로 추가행으로 나눠서 처리함
df = self.replicate_row(df, df['Unnamed: 5'].str.contains("대파") & df['Unnamed: 11'].str.contains("KG"), 5, input_date)
df = self.replicate_row(df, df['Unnamed: 5'].str.contains("오이") & df['Unnamed: 11'].str.contains("KG"), 10, input_date)
(4) 선입고 및 당일입고 처리
- 선입고 처리: 선입고 항목들은 별도의 Excel 파일로 저장함
- 당일입고 처리: 당일 입고 항목들도 별도로 처리되며, 이 역시 Excel 파일로 저장함
earlier_df = df[df['Unnamed: 23'].str.contains("선입고")]
today_df = df[df['Unnamed: 23'].str.contains("당일")]
if len(earlier_df) != 0:
earlier_df.to_excel(f"선입고 {before_input_date}.xls", startrow=1, header=None, index=False)
if len(today_df) != 0:
today_df.to_excel(f"당일입고 {earlier_date}.xls", startrow=1, header=None, index=False)
(5) 쌀 발주서 생성
- 쌀 항목은 따로 처리되어, 별도의 Excel 파일( 쌀 발주서)로 저장됨
rice_df = df[df['Unnamed: 5'] == "쌀"]
if len(rice_df) != 0:
rice_df.to_excel(f"쌀발주서_{datetime.today().strftime('%H%M')}.xls", startrow=1, header=None, index=False)
(6) 최종 발주서 저장
- 모든 데이터를 처리한 후, 최종 발주서를 저장
- 파일명은 날짜와 시간을 기반으로 지정된 형식에 맞춰 저장됨
df.to_excel(f"본발주서 {file_input_date}.xls", startrow=1, header=None, index=False)
3-2. 프로그램 실행
- 프로그램을 실행할 때, 필요한 인덱스(수산, 육류, 선입고 항목 번호 등)를 입력받음
- 입력받은 값에 맞춰
start함수가 실행됨 - 최종적으로 처리된 결과는 여러 Excel 파일로 저장됨