feat: 도매상 설정 중앙 관리 시스템

- config/wholesalers.json: 도매상 정보 중앙 관리 (ID, 이름, 로고, 색상, API)
- config/__init__.py: Python 헬퍼 (get_wholesalers, get_wholesaler)
- wholesaler_config_api.py: /api/config/wholesalers 엔드포인트
- 백제약품 로고(favicon) 추가: logo_baekje.ico
- 잔고 모달에 로고 표시 기능 추가
This commit is contained in:
thug0bin 2026-03-06 17:18:40 +09:00
parent 2d09f139ca
commit ad0b55ee2d
6 changed files with 202 additions and 6 deletions

View File

@ -68,6 +68,9 @@ app.register_blueprint(sooin_bp)
from baekje_api import baekje_bp from baekje_api import baekje_bp
app.register_blueprint(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 from order_api import order_bp
app.register_blueprint(order_bp) app.register_blueprint(order_bp)

View File

@ -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()

View File

@ -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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -2210,6 +2210,20 @@
.balance-icon.geoyoung { background: linear-gradient(135deg, #0891b2, #06b6d4); } .balance-icon.geoyoung { background: linear-gradient(135deg, #0891b2, #06b6d4); }
.balance-icon.sooin { background: linear-gradient(135deg, #7c3aed, #a855f7); } .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 { .balance-info {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
@ -2281,6 +2295,26 @@
document.getElementById('balanceModal').classList.remove('show'); 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() { async function loadBalances() {
const content = document.getElementById('balanceContent'); const content = document.getElementById('balanceContent');
content.innerHTML = ` content.innerHTML = `
@ -2289,11 +2323,7 @@
<div>잔고 조회 중...</div> <div>잔고 조회 중...</div>
</div>`; </div>`;
const wholesalers = [ const wholesalers = WHOLESALER_ORDER.map(id => WHOLESALER_CONFIG[id]);
{ 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 results = {}; const results = {};
let totalBalance = 0; let totalBalance = 0;
@ -2323,7 +2353,11 @@
html += ` html += `
<div class="balance-card ${isError ? 'error' : ''}"> <div class="balance-card ${isError ? 'error' : ''}">
<div class="balance-icon ${ws.id}">${ws.icon}</div> <div class="balance-logo-wrap">
<img src="${ws.logo}" alt="${ws.name}" class="balance-logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="balance-icon ${ws.id}" style="display:none;">${ws.icon}</div>
</div>
<div class="balance-info"> <div class="balance-info">
<div class="balance-name">${ws.name}</div> <div class="balance-name">${ws.name}</div>
<div class="balance-detail"> <div class="balance-detail">

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
"""
도매상 설정 API
GET /api/config/wholesalers - 도매상 목록 (순서대로)
GET /api/config/wholesaler/<id> - 특정 도매상 정보
"""
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/<wholesaler_id>', 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