diff --git a/backend/check_paai_status.py b/backend/check_paai_status.py new file mode 100644 index 0000000..1e76109 --- /dev/null +++ b/backend/check_paai_status.py @@ -0,0 +1,40 @@ +import sqlite3 +import os + +db_path = 'db/paai_logs.db' +print(f"DB 경로: {os.path.abspath(db_path)}") +print(f"파일 존재: {os.path.exists(db_path)}") + +conn = sqlite3.connect(db_path) +cursor = conn.cursor() + +# 모든 테이블 확인 +cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") +all_tables = [t[0] for t in cursor.fetchall()] +print(f"전체 테이블: {all_tables}") + +for table in all_tables: + cursor.execute(f"SELECT COUNT(*) FROM {table}") + count = cursor.fetchone()[0] + print(f"\n=== {table}: {count}건 ===") + + # 컬럼 정보 + cursor.execute(f"PRAGMA table_info({table})") + cols = [c[1] for c in cursor.fetchall()] + print(f"컬럼: {cols}") + + # status 컬럼이 있으면 상태별 카운트 + if 'status' in cols: + cursor.execute(f"SELECT status, COUNT(*) FROM {table} GROUP BY status") + for row in cursor.fetchall(): + print(f" {row[0]}: {row[1]}건") + + # 최근 5건 + cursor.execute(f"SELECT * FROM {table} ORDER BY rowid DESC LIMIT 3") + rows = cursor.fetchall() + if rows: + print(f"최근 3건:") + for row in rows: + print(f" {row}") + +conn.close() diff --git a/backend/check_schema.py b/backend/check_schema.py new file mode 100644 index 0000000..b923f97 --- /dev/null +++ b/backend/check_schema.py @@ -0,0 +1,6 @@ +import sqlite3 +conn = sqlite3.connect('db/paai_logs.db') +cursor = conn.cursor() +cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='paai_logs'") +print(cursor.fetchone()[0]) +conn.close() diff --git a/backend/sooin_api.py b/backend/sooin_api.py index 780c859..93d9cd7 100644 --- a/backend/sooin_api.py +++ b/backend/sooin_api.py @@ -437,3 +437,236 @@ def api_sooin_order_batch(): 'failed_count': failed_count, 'results': results }) + + +# ========== 주문 조회 API ========== + +@sooin_bp.route('/orders', methods=['GET']) +def api_sooin_orders(): + """ + 수인약품 주문 목록 조회 API + + GET /api/sooin/orders?start_date=2026-03-01&end_date=2026-03-07 + + 파라미터: + start_date: 시작일 (YYYY-MM-DD), 기본값: 오늘 + end_date: 종료일 (YYYY-MM-DD), 기본값: 오늘 + + Returns: + { + "success": true, + "orders": [ + { + "order_num": "202603095091177", + "order_date": "2026-03-09", + "order_time": "14:30:25", + "total_amount": 125000, + "item_count": 5, + "status": "완료" + } + ], + "total_count": 10 + } + """ + from datetime import datetime + + today = datetime.now().strftime("%Y-%m-%d") + start_date = flask_request.args.get('start_date', today).strip() + end_date = flask_request.args.get('end_date', today).strip() + + try: + session = get_sooin_session() + result = session.get_order_list(start_date, end_date) + return jsonify(result) + except Exception as e: + logger.error(f"수인약품 주문 목록 조회 오류: {e}") + return jsonify({ + 'success': False, + 'error': 'ORDERS_ERROR', + 'message': str(e), + 'orders': [], + 'total_count': 0 + }), 500 + + +@sooin_bp.route('/orders/today-summary', methods=['GET']) +def api_sooin_today_summary(): + """ + 수인약품 오늘 주문 집계 API + + GET /api/sooin/orders/today-summary + + Returns: + { + "success": true, + "date": "2026-03-09", + "summary": [ + { + "kd_code": "073100220", + "product_name": "코자정50mg", + "total_quantity": 10, + "total_amount": 150000, + "order_count": 3 + } + ], + "grand_total_amount": 500000, + "grand_total_items": 25, + "order_count": 5 + } + """ + try: + session = get_sooin_session() + result = session.get_today_order_summary() + return jsonify(result) + except Exception as e: + logger.error(f"수인약품 오늘 주문 집계 오류: {e}") + return jsonify({ + 'success': False, + 'error': 'TODAY_SUMMARY_ERROR', + 'message': str(e), + 'summary': [], + 'grand_total_amount': 0, + 'grand_total_items': 0, + 'order_count': 0 + }), 500 + + +@sooin_bp.route('/orders/', methods=['GET']) +def api_sooin_order_detail(order_num): + """ + 수인약품 주문 상세 조회 API + + GET /api/sooin/orders/202603095091177 + + Returns: + { + "success": true, + "order_num": "202603095091177", + "order_date": "2026-03-09", + "items": [ + { + "product_code": "32495", + "kd_code": "073100220", + "product_name": "코자정50mg", + "spec": "30T", + "quantity": 2, + "unit_price": 15000, + "amount": 30000 + } + ], + "total_amount": 125000, + "item_count": 5 + } + """ + if not order_num or not order_num.isdigit(): + return jsonify({ + 'success': False, + 'error': 'INVALID_ORDER_NUM', + 'message': '유효한 주문번호를 입력하세요' + }), 400 + + try: + session = get_sooin_session() + result = session.get_order_detail(order_num) + return jsonify(result) + except Exception as e: + logger.error(f"수인약품 주문 상세 조회 오류: {e}") + return jsonify({ + 'success': False, + 'error': 'ORDER_DETAIL_ERROR', + 'message': str(e), + 'order_num': order_num, + 'items': [], + 'total_amount': 0 + }), 500 + + +@sooin_bp.route('/orders/summary-by-kd', methods=['GET']) +def api_sooin_orders_by_kd(): + """ + 수인약품 주문량 KD코드별 집계 API (병렬 처리) + + GET /api/sooin/orders/summary-by-kd?start_date=2026-03-01&end_date=2026-03-07 + """ + import re + from datetime import datetime + + today = datetime.now().strftime("%Y-%m-%d") + start_date = flask_request.args.get('start_date', today).strip() + end_date = flask_request.args.get('end_date', today).strip() + + def parse_spec(spec: str) -> int: + if not spec: + return 1 + match = re.search(r'(\d+)', spec) + return int(match.group(1)) if match else 1 + + try: + session = get_sooin_session() + + # 주문 목록 조회 + orders_result = session.get_order_list(start_date, end_date) + + if not orders_result.get('success'): + return jsonify({ + 'success': False, + 'error': orders_result.get('error', 'ORDERS_FETCH_FAILED'), + 'by_kd_code': {} + }) + + orders = orders_result.get('orders', []) + order_nums = [o.get('order_num') for o in orders if o.get('order_num')] + + # 순차 처리 + 캐시 (캐시 효과 극대화) + all_details = [] + + for order_num in order_nums: + try: + detail = session.get_order_detail(order_num) + if detail.get('success'): + all_details.append(detail) + except Exception as e: + logger.warning(f"주문 상세 조회 실패: {e}") + + # KD코드별 집계 + kd_summary = {} + + for detail in all_details: + for item in detail.get('items', []): + kd_code = item.get('kd_code', '') + if not kd_code: + continue + + product_name = item.get('product_name', '') + spec = item.get('spec', '') + quantity = item.get('quantity', 0) + per_unit = parse_spec(spec) + total_units = quantity * per_unit + + if kd_code not in kd_summary: + kd_summary[kd_code] = { + 'product_name': product_name, + 'spec': spec, + 'boxes': 0, + 'units': 0 + } + + kd_summary[kd_code]['boxes'] += quantity + kd_summary[kd_code]['units'] += total_units + + return jsonify({ + 'success': True, + 'order_count': len(order_nums), + 'period': {'start': start_date, 'end': end_date}, + 'by_kd_code': kd_summary, + 'total_products': len(kd_summary) + }) + + except Exception as e: + logger.error(f"수인약품 KD코드별 집계 오류: {e}") + return jsonify({ + 'success': False, + 'error': 'SUMMARY_ERROR', + 'message': str(e), + 'by_kd_code': {} + }), 500 diff --git a/backend/templates/admin_members.html b/backend/templates/admin_members.html index 51607dd..9b4c2ea 100644 --- a/backend/templates/admin_members.html +++ b/backend/templates/admin_members.html @@ -924,7 +924,8 @@ const txs = detailData.mileage.transactions; container.innerHTML = txs.map(tx => { const isPositive = tx.points > 0; - const date = tx.created_at ? new Date(tx.created_at).toLocaleString('ko-KR', { + // DB는 UTC로 저장 → 'Z' 붙여서 UTC로 해석 → KST로 표시 + const date = tx.created_at ? new Date(tx.created_at + 'Z').toLocaleString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : ''; @@ -1075,8 +1076,8 @@ } container.innerHTML = detailData.interests.map(item => { - // 날짜 포맷 - const date = item.created_at ? new Date(item.created_at).toLocaleString('ko-KR', { + // 날짜 포맷 (DB는 UTC → KST 변환) + const date = item.created_at ? new Date(item.created_at + 'Z').toLocaleString('ko-KR', { month: 'short', day: 'numeric' }) : ''; diff --git a/backend/templates/admin_paai.html b/backend/templates/admin_paai.html index fab206c..116f6e1 100644 --- a/backend/templates/admin_paai.html +++ b/backend/templates/admin_paai.html @@ -358,7 +358,8 @@ } tbody.innerHTML = data.logs.map(log => { - const time = new Date(log.created_at).toLocaleString('ko-KR', { + // DB는 UTC로 저장 → 'Z' 붙여서 UTC로 해석 → KST로 표시 + const time = new Date(log.created_at + 'Z').toLocaleString('ko-KR', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); diff --git a/backend/templates/admin_rx_usage.html b/backend/templates/admin_rx_usage.html index 569163f..2b1d257 100644 --- a/backend/templates/admin_rx_usage.html +++ b/backend/templates/admin_rx_usage.html @@ -37,6 +37,17 @@ min-height: 100vh; } + /* ══════════════════ 주문량 로딩 ══════════════════ */ + .order-loading { + display: inline-block; + color: var(--accent-cyan); + animation: pulse 1s infinite; + } + @keyframes pulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } + } + /* ══════════════════ 헤더 ══════════════════ */ .header { background: linear-gradient(135deg, #0891b2 0%, #06b6d4 50%, #22d3ee 100%); @@ -825,17 +836,18 @@ - 약품 + 약품 현재고 처방횟수 투약량 + 주문량 매출액 주문수량 - +
데이터 로딩 중...
@@ -882,6 +894,7 @@