From ad0b55ee2d4adcb69542f8b19f0c0d6e27f106de Mon Sep 17 00:00:00 2001 From: thug0bin Date: Fri, 6 Mar 2026 17:18:40 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8F=84=EB=A7=A4=EC=83=81=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91=EC=95=99=20=EA=B4=80=EB=A6=AC=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - config/wholesalers.json: 도매상 정보 중앙 관리 (ID, 이름, 로고, 색상, API) - config/__init__.py: Python 헬퍼 (get_wholesalers, get_wholesaler) - wholesaler_config_api.py: /api/config/wholesalers 엔드포인트 - 백제약품 로고(favicon) 추가: logo_baekje.ico - 잔고 모달에 로고 표시 기능 추가 --- backend/app.py | 3 ++ backend/config/__init__.py | 54 +++++++++++++++++++++ backend/config/wholesalers.json | 65 ++++++++++++++++++++++++++ backend/static/img/logo_baekje.ico | Bin 0 -> 1150 bytes backend/templates/admin_rx_usage.html | 46 +++++++++++++++--- backend/wholesaler_config_api.py | 40 ++++++++++++++++ 6 files changed, 202 insertions(+), 6 deletions(-) create mode 100644 backend/config/__init__.py create mode 100644 backend/config/wholesalers.json create mode 100644 backend/static/img/logo_baekje.ico create mode 100644 backend/wholesaler_config_api.py diff --git a/backend/app.py b/backend/app.py index e94084a..5180f00 100644 --- a/backend/app.py +++ b/backend/app.py @@ -68,6 +68,9 @@ app.register_blueprint(sooin_bp) from baekje_api import baekje_bp app.register_blueprint(baekje_bp) +from wholesaler_config_api import wholesaler_config_bp +app.register_blueprint(wholesaler_config_bp) + from order_api import order_bp app.register_blueprint(order_bp) diff --git a/backend/config/__init__.py b/backend/config/__init__.py new file mode 100644 index 0000000..151856f --- /dev/null +++ b/backend/config/__init__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +""" +도매상 설정 중앙 관리 + +사용법: + from config import get_wholesalers, get_wholesaler + + # 전체 도매상 목록 + wholesalers = get_wholesalers() + + # 특정 도매상 정보 + geo = get_wholesaler('geoyoung') + print(geo['name']) # 지오영 + print(geo['logo']) # /static/img/logo_geoyoung.ico +""" + +import json +from pathlib import Path + +_config = None +_config_path = Path(__file__).parent / 'wholesalers.json' + + +def _load_config(): + global _config + if _config is None: + with open(_config_path, 'r', encoding='utf-8') as f: + _config = json.load(f) + return _config + + +def get_wholesalers(): + """전체 도매상 목록 반환 (순서대로)""" + config = _load_config() + order = config.get('order', []) + wholesalers = config.get('wholesalers', {}) + return [wholesalers[key] for key in order if key in wholesalers] + + +def get_wholesaler(wholesaler_id: str): + """특정 도매상 정보 반환""" + config = _load_config() + return config.get('wholesalers', {}).get(wholesaler_id) + + +def get_all_wholesalers_dict(): + """전체 도매상 딕셔너리 반환""" + config = _load_config() + return config.get('wholesalers', {}) + + +def get_config(): + """전체 설정 반환""" + return _load_config() diff --git a/backend/config/wholesalers.json b/backend/config/wholesalers.json new file mode 100644 index 0000000..e777eb4 --- /dev/null +++ b/backend/config/wholesalers.json @@ -0,0 +1,65 @@ +{ + "wholesalers": { + "geoyoung": { + "id": "geoyoung", + "name": "지오영", + "shortName": "지오영", + "icon": "🏭", + "logo": "/static/img/logo_geoyoung.ico", + "color": "#06b6d4", + "gradient": "linear-gradient(135deg, #0891b2, #06b6d4)", + "bgColor": "rgba(6, 182, 212, 0.1)", + "api": { + "balance": "/api/geoyoung/balance", + "stock": "/api/geoyoung/stock", + "order": "/api/geoyoung/order" + }, + "env": { + "userId": "GEOYOUNG_USER_ID", + "password": "GEOYOUNG_PASSWORD" + } + }, + "sooin": { + "id": "sooin", + "name": "수인약품", + "shortName": "수인", + "icon": "💊", + "logo": "/static/img/logo_sooin.svg", + "color": "#a855f7", + "gradient": "linear-gradient(135deg, #7c3aed, #a855f7)", + "bgColor": "rgba(168, 85, 247, 0.1)", + "api": { + "balance": "/api/sooin/balance", + "stock": "/api/sooin/stock", + "order": "/api/sooin/order" + }, + "env": { + "userId": "SOOIN_USER_ID", + "password": "SOOIN_PASSWORD", + "vendorCode": "SOOIN_VENDOR_CODE" + } + }, + "baekje": { + "id": "baekje", + "name": "백제약품", + "shortName": "백제", + "icon": "💉", + "logo": "/static/img/logo_baekje.ico", + "color": "#f59e0b", + "gradient": "linear-gradient(135deg, #d97706, #f59e0b)", + "bgColor": "rgba(245, 158, 11, 0.1)", + "api": { + "balance": "/api/baekje/balance", + "stock": "/api/baekje/stock", + "order": "/api/baekje/order" + }, + "env": { + "userId": "BAEKJE_USER_ID", + "password": "BAEKJE_PASSWORD" + } + } + }, + "order": ["baekje", "geoyoung", "sooin"], + "version": "1.0.0", + "lastUpdated": "2026-03-06" +} diff --git a/backend/static/img/logo_baekje.ico b/backend/static/img/logo_baekje.ico new file mode 100644 index 0000000000000000000000000000000000000000..379d9a17d9d8dae13be7d5028eb4632294d0f4d8 GIT binary patch literal 1150 zcmaizNla5w6ozln#9iacwM+L+3KWpRQh@^MLXDc3xHAq=6p*SQ3Zl{qsDL#lE;J^N ziJFk03xkOgh1bc_@qy4Xm7yx&fR^z4zt)tMvCY?WbKCRvoIkA)Dn2)E6nxz*4zCwt zlMrGH;T3O^_bfv2y?;F%rVQaQWu`eQGc=BhbgR80U9un7M(xKENYYWQm1qW!Xwz6z zn%g`%t92wx?L46~JE}4xbgj|@1$tmVPL63;Nrae*nh1%#?o-*qU6-SF)n?mVHCb`z zNgbRg*jq(R>2$ESLQ982!WCz=5j7FM7QO1@>8)nBIcJ3~ca*#y@loZN&JdU;TP-j{6ylsJU5Q< zr`F_5Rhg_AVWJs2Utk$(%!lV(9)`~A@x}EDpB=sUHtNP==mQpf@9;h1MQpGK^Y5R- zRg(?oh2)+(i?>-Xd7HLj=t4gHmYrA{9>m<+2UwH`@YVA=<(lccj|G1}JPii!k(Hz- zOY&VRjQUy%;k~HG?pAW+e;8^E2hJM@h4|EM>_&-rI5Q1 zK69}eFv7bCVEX{;Jz9quld=!(*# hAkkyxTuN4HB-J;zL5RL}LNuyUSN}aIpH+I4-ak)ei(3Ey literal 0 HcmV?d00001 diff --git a/backend/templates/admin_rx_usage.html b/backend/templates/admin_rx_usage.html index f11d6fc..dc50150 100644 --- a/backend/templates/admin_rx_usage.html +++ b/backend/templates/admin_rx_usage.html @@ -2210,6 +2210,20 @@ .balance-icon.geoyoung { background: linear-gradient(135deg, #0891b2, #06b6d4); } .balance-icon.sooin { background: linear-gradient(135deg, #7c3aed, #a855f7); } + .balance-logo-wrap { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + } + .balance-logo { + width: 40px; + height: 40px; + object-fit: contain; + border-radius: 8px; + } + .balance-info { flex: 1; min-width: 0; @@ -2281,6 +2295,26 @@ document.getElementById('balanceModal').classList.remove('show'); } + // 도매상 설정 (중앙 관리) + const WHOLESALER_CONFIG = { + baekje: { + id: 'baekje', name: '백제약품', icon: '💉', + logo: '/static/img/logo_baekje.ico', + color: '#f59e0b', api: '/api/baekje/balance' + }, + geoyoung: { + id: 'geoyoung', name: '지오영', icon: '🏭', + logo: '/static/img/logo_geoyoung.ico', + color: '#06b6d4', api: '/api/geoyoung/balance' + }, + sooin: { + id: 'sooin', name: '수인약품', icon: '💊', + logo: '/static/img/logo_sooin.svg', + color: '#a855f7', api: '/api/sooin/balance' + } + }; + const WHOLESALER_ORDER = ['baekje', 'geoyoung', 'sooin']; + async function loadBalances() { const content = document.getElementById('balanceContent'); content.innerHTML = ` @@ -2289,11 +2323,7 @@
잔고 조회 중...
`; - const wholesalers = [ - { id: 'baekje', name: '백제약품', icon: '💉', api: '/api/baekje/balance' }, - { id: 'geoyoung', name: '지오영', icon: '🏭', api: '/api/geoyoung/balance' }, - { id: 'sooin', name: '수인약품', icon: '💊', api: '/api/sooin/balance' } - ]; + const wholesalers = WHOLESALER_ORDER.map(id => WHOLESALER_CONFIG[id]); const results = {}; let totalBalance = 0; @@ -2323,7 +2353,11 @@ html += `
-
${ws.icon}
+
+ + +
${ws.name}
diff --git a/backend/wholesaler_config_api.py b/backend/wholesaler_config_api.py new file mode 100644 index 0000000..e8f50f6 --- /dev/null +++ b/backend/wholesaler_config_api.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +도매상 설정 API + +GET /api/config/wholesalers - 도매상 목록 (순서대로) +GET /api/config/wholesaler/ - 특정 도매상 정보 +""" + +from flask import Blueprint, jsonify +from config import get_wholesalers, get_wholesaler, get_config + +wholesaler_config_bp = Blueprint('wholesaler_config', __name__, url_prefix='/api/config') + + +@wholesaler_config_bp.route('/wholesalers', methods=['GET']) +def api_get_wholesalers(): + """도매상 목록 반환""" + wholesalers = get_wholesalers() + return jsonify({ + 'success': True, + 'wholesalers': wholesalers, + 'count': len(wholesalers) + }) + + +@wholesaler_config_bp.route('/wholesaler/', methods=['GET']) +def api_get_wholesaler(wholesaler_id): + """특정 도매상 정보 반환""" + wholesaler = get_wholesaler(wholesaler_id) + if wholesaler: + return jsonify({ + 'success': True, + 'wholesaler': wholesaler + }) + else: + return jsonify({ + 'success': False, + 'error': 'NOT_FOUND', + 'message': f'도매상 {wholesaler_id}을(를) 찾을 수 없습니다' + }), 404