버그 수정: 관리자 권한 DB 불일치 우회 및 다중 엑셀 파일 업로드 기능 구현

This commit is contained in:
2026-05-26 21:50:19 +09:00
parent 274507e2af
commit b2ecd896f5
4 changed files with 99 additions and 82 deletions
+89 -74
View File
@@ -420,82 +420,87 @@ def orders_to_excel_response(orders, filename: str, sheet_name: str = "Orders"):
)
def run_upload_job(contents: bytes, filename: str, user_id: str, user_data: dict):
def run_upload_job(files_data: list[dict], user_id: str, user_data: dict):
current_user = SimpleNamespace(**user_data)
db = SessionLocal()
filenames_str = ", ".join([f["filename"] for f in files_data])
try:
set_upload_state(
user_id,
status="reading",
message="엑셀 파일을 읽는 중입니다...",
)
# read_excel requires xlrd for .xls and openpyxl for .xlsx
df = pd.read_excel(io.BytesIO(contents))
# Replace NaN with None for SQLAlchemy
df = df.where(pd.notnull(df), None)
column_map = find_template_columns(list(df.columns))
total_rows = len(df)
set_upload_state(
user_id,
status="processing",
message=f"{total_rows}건의 엑셀 데이터를 정리하는 중입니다...",
total_rows=total_rows,
)
db_records = []
batch_size = 1000
total_rows = 0
for index, row in df.iterrows():
rlist = list(row)
recipient_phone = row_value(rlist, column_map, "recipient_phone")
recipient_mobile = row_value(rlist, column_map, "recipient_mobile")
tracking_number = row_value(rlist, column_map, "tracking_number")
address = row_value(rlist, column_map, "address")
# 1단계: 모든 파일 읽기 및 행 데이터 정규화
for f_idx, file_item in enumerate(files_data):
contents = file_item["contents"]
filename = file_item["filename"]
order = Order(
order_date=row_value(rlist, column_map, "order_date"),
sequence_num=row_value(rlist, column_map, "sequence_num"),
order_no=row_value(rlist, column_map, "order_no"),
order_no_mall=row_value(rlist, column_map, "order_no_mall"),
recipient_name=row_value(rlist, column_map, "recipient_name"),
product_code=row_value(rlist, column_map, "product_code"),
product_name=row_value(rlist, column_map, "product_name"),
order_quantity=row_value(rlist, column_map, "order_quantity"),
address=address,
address_normalized=normalize_address_key(address),
postal_code=row_value(rlist, column_map, "postal_code"),
recipient_phone=recipient_phone,
recipient_mobile=recipient_mobile,
recipient_phone_normalized=normalize_phone(recipient_phone),
recipient_mobile_normalized=normalize_phone(recipient_mobile),
delivery_memo=row_value(rlist, column_map, "delivery_memo"),
tracking_number=tracking_number,
tracking_number_normalized=normalize_tracking(tracking_number),
vendor=row_value(rlist, column_map, "vendor"),
order_list_1=row_value(rlist, column_map, "order_list_1"),
order_note=row_value(rlist, column_map, "order_note"),
upload_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
set_upload_state(
user_id,
status="reading",
message=f"[{f_idx + 1}/{len(files_data)}] 엑셀 파일을 읽는 중입니다 ({filename})...",
)
db_records.append(order)
# Update progress periodically
if index % 1000 == 0 and index > 0:
calc_progress = float(index) / total_rows if total_rows else 1
df = pd.read_excel(io.BytesIO(contents))
df = df.where(pd.notnull(df), None)
column_map = find_template_columns(list(df.columns))
set_upload_state(
user_id,
status="processing",
message=f"{index}/{total_rows}건 엑셀 데이터 정리 중...",
upload_progress=calc_progress * 100,
total_rows=total_rows,
processed_rows=index,
file_rows = len(df)
total_rows += file_rows
set_upload_state(
user_id,
status="processing",
message=f"[{f_idx + 1}/{len(files_data)}] {filename}의 엑셀 데이터를 정리하는 중...",
total_rows=total_rows,
)
for index, row in df.iterrows():
rlist = list(row)
recipient_phone = row_value(rlist, column_map, "recipient_phone")
recipient_mobile = row_value(rlist, column_map, "recipient_mobile")
tracking_number = row_value(rlist, column_map, "tracking_number")
address = row_value(rlist, column_map, "address")
order = Order(
order_date=row_value(rlist, column_map, "order_date"),
sequence_num=row_value(rlist, column_map, "sequence_num"),
order_no=row_value(rlist, column_map, "order_no"),
order_no_mall=row_value(rlist, column_map, "order_no_mall"),
recipient_name=row_value(rlist, column_map, "recipient_name"),
product_code=row_value(rlist, column_map, "product_code"),
product_name=row_value(rlist, column_map, "product_name"),
order_quantity=row_value(rlist, column_map, "order_quantity"),
address=address,
address_normalized=normalize_address_key(address),
postal_code=row_value(rlist, column_map, "postal_code"),
recipient_phone=recipient_phone,
recipient_mobile=recipient_mobile,
recipient_phone_normalized=normalize_phone(recipient_phone),
recipient_mobile_normalized=normalize_phone(recipient_mobile),
delivery_memo=row_value(rlist, column_map, "delivery_memo"),
tracking_number=tracking_number,
tracking_number_normalized=normalize_tracking(tracking_number),
vendor=row_value(rlist, column_map, "vendor"),
order_list_1=row_value(rlist, column_map, "order_list_1"),
order_note=row_value(rlist, column_map, "order_note"),
upload_date=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
db_records.append(order)
# 주기적으로 데이터 정리 진행률 갱신 (전체 누적 건수 기준)
current_processed = len(db_records)
if current_processed % 1000 == 0 and current_processed > 0:
calc_progress = float(current_processed) / total_rows if total_rows else 1
set_upload_state(
user_id,
status="processing",
message=f"전체 {current_processed}건 엑셀 데이터 정리 중...",
upload_progress=calc_progress * 100,
total_rows=total_rows,
processed_rows=current_processed,
)
# 2단계: FTS 인덱스 및 DB 저장 단계
set_upload_state(
user_id,
status="indexing",
@@ -506,6 +511,7 @@ def run_upload_job(contents: bytes, filename: str, user_id: str, user_data: dict
processed_rows=total_rows,
)
batch_size = 1000
for i in range(0, len(db_records), batch_size):
chunk = db_records[i:i + batch_size]
db.bulk_save_objects(chunk)
@@ -538,39 +544,48 @@ def run_upload_job(contents: bytes, filename: str, user_id: str, user_data: dict
current_user,
"파일 업로드 완료",
"success",
{"filename": filename, "uploaded_count": len(db_records)},
{"filenames": filenames_str, "uploaded_count": len(db_records)},
)
except ValueError as e:
db.rollback()
set_upload_error(user_id, str(e))
log_event(current_user, "파일 업로드 실패", "error", {"filename": filename, "error": str(e)})
log_event(current_user, "파일 업로드 실패", "error", {"filenames": filenames_str, "error": str(e)})
except Exception as e:
db.rollback()
set_upload_error(user_id, f"에러 발생: {str(e)}")
log_event(current_user, "파일 업로드 실패", "error", {"filename": filename, "error": str(e)})
log_event(current_user, "파일 업로드 실패", "error", {"filenames": filenames_str, "error": str(e)})
finally:
db.close()
@router.post("/upload")
def upload_file(
file: UploadFile = File(...),
files: list[UploadFile] = File(...),
upload_token: str = Form(...),
current_user = Depends(get_current_user)
):
filename = file.filename or ""
if not filename.endswith(".xls") and not filename.endswith(".xlsx"):
raise HTTPException(status_code=400, detail="Only XLS or XLSX files are allowed.")
for file in files:
filename = file.filename or ""
if not filename.endswith(".xls") and not filename.endswith(".xlsx"):
raise HTTPException(status_code=400, detail="XLS 또는 XLSX 파일만 업로드할 수 있습니다.")
user_id = f"{current_user.id}:{upload_token}"
contents = file.file.read()
files_data = []
for file in files:
contents = file.file.read()
files_data.append({
"contents": contents,
"filename": file.filename or ""
})
user_data = {
"id": getattr(current_user, "id", ""),
"email": getattr(current_user, "email", None),
"name": getattr(current_user, "name", None),
}
log_event(current_user, "파일 업로드 시작", "started", {"filename": filename})
filenames_str = ", ".join([f["filename"] for f in files_data])
log_event(current_user, "파일 업로드 시작", "started", {"filenames": filenames_str})
set_upload_state(
user_id,
status="queued",
@@ -578,7 +593,7 @@ def upload_file(
)
threading.Thread(
target=run_upload_job,
args=(contents, filename, user_id, user_data),
args=(files_data, user_id, user_data),
daemon=True,
).start()