init: dbx-corm 프로젝트 Docker 기반 마이그레이션

주요 변경사항:
- DB 접속정보를 환경변수(.env) 방식으로 분리
- orderlist_app → orderlist_db 변경 (main.py, routers/returns.py)
- 네이버/카페24 API 키도 환경변수로 분리
- Dockerfile / docker-compose.yml 작성 (외부 PostgreSQL 네트워크 연결)
- .gitignore: 민감파일(.env, cafe24_tokens.json, manual-ordering.json) 제외
- 불필요 파일 정리 (venv, __pycache__, 일회성 스크립트, xlsx 등)
- README.md / .env.example / push_to_gitea.bat 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:11:34 +09:00
commit 6e438344d0
20 changed files with 8179 additions and 0 deletions
View File
+276
View File
@@ -0,0 +1,276 @@
import os
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from typing import Optional, List
import psycopg2
import psycopg2.extras
import datetime
router = APIRouter(prefix="/api/return", tags=["Returns"])
# 환경변수 기반 DB 설정
_PG_HOST = os.getenv('POSTGRES_HOST', 'postgres-db')
_PG_PORT = int(os.getenv('POSTGRES_PORT', '5432'))
_PG_USER = os.getenv('POSTGRES_USER', 'king')
_PG_PASSWORD = os.getenv('POSTGRES_PASSWORD', '')
RETURN_DB_CONFIG = {
'host': _PG_HOST,
'port': _PG_PORT,
'dbname': os.getenv('RETURN_DB', 'return_db'),
'user': _PG_USER,
'password': _PG_PASSWORD,
}
def get_return_db_conn():
return psycopg2.connect(**RETURN_DB_CONFIG, cursor_factory=psycopg2.extras.DictCursor)
ORDER_DB_CONFIG = {
'host': _PG_HOST,
'port': _PG_PORT,
'dbname': os.getenv('ORDERLIST_DB', 'orderlist_db'),
'user': _PG_USER,
'password': _PG_PASSWORD,
}
def get_order_db_conn():
return psycopg2.connect(**ORDER_DB_CONFIG, cursor_factory=psycopg2.extras.DictCursor)
class ReturnRequest(BaseModel):
id: Optional[int] = None
request_date: str
request_type: str
receiver_name: str
address: str
phone: str
tracking_no: str
mall: str
remarks: str
class ReturnReceive(BaseModel):
id: Optional[int] = None
receive_date: str
tracking_no: str
mall: str
receiver_name: str
phone: str
address: Optional[str] = ''
remarks: str
receive_status: str
class BulkReturnReceive(BaseModel):
tracking_numbers: List[str]
@router.get("/lookup")
async def return_lookup(tracking_no: str):
try:
conn = get_order_db_conn()
cur = conn.cursor()
# 주문일시(order_date 또는 upload_date) 기준 가장 최신 1건 조회
cur.execute("""
SELECT vendor, recipient_name, recipient_phone, recipient_mobile, address
FROM orders
WHERE tracking_number = %s
ORDER BY order_date DESC LIMIT 1
""", (tracking_no,))
row = cur.fetchone()
conn.close()
if row:
return {"success": True, "data": dict(row)}
return {"success": False, "message": "송장번호를 찾을 수 없습니다."}
except Exception as e:
print(f"Lookup error: {e}")
return {"success": False, "message": str(e)}
@router.post("/request")
async def create_return_request(req: ReturnRequest):
try:
conn = get_return_db_conn()
cur = conn.cursor()
if req.id:
cur.execute("""
UPDATE return_requests
SET request_date=%s, request_type=%s, receiver_name=%s, address=%s, phone=%s, tracking_no=%s, mall=%s, remarks=%s
WHERE id=%s
""", (req.request_date, req.request_type, req.receiver_name, req.address, req.phone, req.tracking_no, req.mall, req.remarks, req.id))
else:
cur.execute("""
INSERT INTO return_requests
(request_date, request_type, receiver_name, address, phone, tracking_no, mall, remarks)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (req.request_date, req.request_type, req.receiver_name, req.address, req.phone, req.tracking_no, req.mall, req.remarks))
conn.commit()
conn.close()
return {"success": True}
except Exception as e:
print(f"Request error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/request")
async def get_return_requests(tracking_no: Optional[str] = None):
try:
conn = get_return_db_conn()
cur = conn.cursor()
if tracking_no:
cur.execute("SELECT * FROM return_requests WHERE tracking_no = %s ORDER BY created_at DESC LIMIT 1", (tracking_no,))
else:
cur.execute("SELECT * FROM return_requests ORDER BY id DESC LIMIT 50")
rows = cur.fetchall()
conn.close()
return {"success": True, "data": [dict(r) for r in rows]}
except Exception as e:
return {"success": False, "message": str(e)}
@router.post("/receive/bulk")
async def create_return_receive_bulk(req: BulkReturnReceive):
try:
from datetime import datetime
today = datetime.now().strftime("%Y-%m-%d")
conn_return = get_return_db_conn()
cur_return = conn_return.cursor()
conn_order = get_order_db_conn()
cur_order = conn_order.cursor()
success_count = 0
for tracking_no in req.tracking_numbers:
tracking_no = tracking_no.strip()
if not tracking_no:
continue
mall = ""
receiver_name = ""
phone = ""
address = ""
# 1. Check return_requests
cur_return.execute("SELECT mall, receiver_name, phone, address, request_type, remarks FROM return_requests WHERE tracking_no = %s", (tracking_no,))
req_row = cur_return.fetchone()
if req_row:
mall = req_row['mall'] or ""
receiver_name = req_row['receiver_name'] or ""
phone = req_row['phone'] or ""
address = req_row['address'] or ""
req_type = req_row['request_type'] or ""
req_remarks = req_row['remarks'] or ""
if req_remarks:
remarks = f"{req_type}/{req_remarks}"
else:
remarks = req_type
else:
remarks = "고객이 반품"
# 2. Check orders
cur_order.execute("""
SELECT vendor, recipient_name, recipient_phone, recipient_mobile, address
FROM orders
WHERE tracking_number = %s
ORDER BY order_date DESC LIMIT 1
""", (tracking_no,))
ord_row = cur_order.fetchone()
if ord_row:
mall = ord_row['vendor'] or ""
receiver_name = ord_row['recipient_name'] or ""
phone = ord_row['recipient_phone'] or ord_row['recipient_mobile'] or ""
address = ord_row['address'] or ""
# Insert into return_receivings
cur_return.execute("""
INSERT INTO return_receivings
(receive_date, tracking_no, mall, receiver_name, phone, address, remarks, receive_status)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (today, tracking_no, mall, receiver_name, phone, address, remarks, "환불 대기"))
success_count += 1
conn_return.commit()
conn_return.close()
conn_order.close()
return {"success": True, "count": success_count}
except Exception as e:
print(f"Bulk insert error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/receive")
async def create_return_receive(req: ReturnReceive):
try:
conn = get_return_db_conn()
cur = conn.cursor()
if not req.id:
cur.execute("SELECT request_type, remarks FROM return_requests WHERE tracking_no = %s", (req.tracking_no,))
req_row = cur.fetchone()
if not req_row:
if req.remarks:
if "고객이 반품" not in req.remarks:
req.remarks = "고객이 반품 / " + req.remarks
else:
req.remarks = "고객이 반품"
else:
req_type = req_row['request_type'] or ""
req_remarks = req_row['remarks'] or ""
auto_remark = f"{req_type}/{req_remarks}" if req_remarks else req_type
if not req.remarks:
req.remarks = auto_remark
elif auto_remark not in req.remarks:
req.remarks = f"{auto_remark} / {req.remarks}"
if req.id:
# Update existing record
cur.execute("""
UPDATE return_receivings
SET receive_date=%s, tracking_no=%s, mall=%s, receiver_name=%s, phone=%s, address=%s, remarks=%s, receive_status=%s
WHERE id=%s
""", (req.receive_date, req.tracking_no, req.mall, req.receiver_name, req.phone, req.address, req.remarks, req.receive_status, req.id))
else:
# Insert new record
cur.execute("""
INSERT INTO return_receivings
(receive_date, tracking_no, mall, receiver_name, phone, address, remarks, receive_status)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (req.receive_date, req.tracking_no, req.mall, req.receiver_name, req.phone, req.address, req.remarks, req.receive_status))
conn.commit()
conn.close()
return {"success": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/receive")
async def get_return_receives():
try:
conn = get_return_db_conn()
cur = conn.cursor()
cur.execute("SELECT * FROM return_receivings ORDER BY receive_date DESC, mall ASC, id DESC LIMIT 50")
rows = cur.fetchall()
conn.close()
return {"success": True, "data": [dict(r) for r in rows]}
except Exception as e:
return {"success": False, "message": str(e)}
@router.delete("/request/{item_id}")
async def delete_return_request(item_id: int):
try:
conn = get_return_db_conn()
cur = conn.cursor()
cur.execute("DELETE FROM return_requests WHERE id = %s", (item_id,))
conn.commit()
conn.close()
return {"success": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/receive/{item_id}")
async def delete_return_receive(item_id: int):
try:
conn = get_return_db_conn()
cur = conn.cursor()
cur.execute("DELETE FROM return_receivings WHERE id = %s", (item_id,))
conn.commit()
conn.close()
return {"success": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))