feat(pos-live): 고객 검색/매핑 + 비동기 마일리지 표시
- GET /api/customers/search: CD_PERSON 검색 (최근 활동순)
- PUT /api/pos-live/{order}/customer: SALE_MAIN 고객 업데이트
- GET /api/customers/{code}/mileage: 비동기 마일리지 조회
- UI: 고객 뱃지 클릭 → 검색 모달 → 선택 → 업데이트
- 마일리지: 이름+전화뒤4자리 매칭, 비동기 표시
This commit is contained in:
215
backend/app.py
215
backend/app.py
@@ -6467,7 +6467,8 @@ def api_admin_pos_live():
|
||||
M.SL_NO_order,
|
||||
M.InsertTime,
|
||||
M.SL_MY_sale,
|
||||
ISNULL(M.SL_NM_custom, '[비고객]') AS customer_name,
|
||||
ISNULL(M.SL_NM_custom, '') AS customer_name,
|
||||
M.SL_CD_custom AS customer_code,
|
||||
ISNULL(S.card_total, 0) AS card_total,
|
||||
ISNULL(S.cash_total, 0) AS cash_total,
|
||||
ISNULL(M.SL_MY_total, 0) AS total_amount,
|
||||
@@ -6495,7 +6496,7 @@ def api_admin_pos_live():
|
||||
total_sales = 0
|
||||
|
||||
for row in rows:
|
||||
order_no, insert_time, sale_amount, customer, card_total, cash_total, total_amount, discount, cash_receipt_mode, cash_receipt_num = row
|
||||
order_no, insert_time, sale_amount, customer, customer_code, card_total, cash_total, total_amount, discount, cash_receipt_mode, cash_receipt_num = row
|
||||
|
||||
# 품목 수 조회 (SALE_SUB)
|
||||
mssql_cursor.execute("""
|
||||
@@ -6579,7 +6580,8 @@ def api_admin_pos_live():
|
||||
'amount': sale_amt,
|
||||
'discount': disc_amt,
|
||||
'total_before_dc': total_amt,
|
||||
'customer': customer,
|
||||
'customer': customer if customer else '',
|
||||
'customer_code': customer_code if customer_code else '0000000000',
|
||||
'pay_method': pay_method,
|
||||
'paid': paid,
|
||||
'item_count': item_count,
|
||||
@@ -6665,6 +6667,213 @@ def api_admin_pos_live_detail(order_no):
|
||||
mssql_conn.close()
|
||||
|
||||
|
||||
@app.route('/api/customers/<cus_code>/mileage')
|
||||
def api_customer_mileage(cus_code):
|
||||
"""
|
||||
고객 마일리지 조회 API (비동기)
|
||||
- CD_PERSON에서 이름+전화번호 조회
|
||||
- SQLite users와 이름+전화뒤4자리로 매칭
|
||||
"""
|
||||
if not cus_code or cus_code == '0000000000':
|
||||
return jsonify({'success': False, 'mileage': None})
|
||||
|
||||
mssql_conn = None
|
||||
try:
|
||||
# 1. CD_PERSON에서 이름, 전화번호 조회
|
||||
mssql_engine = db_manager.get_engine('PM_BASE')
|
||||
mssql_conn = mssql_engine.raw_connection()
|
||||
cursor = mssql_conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT PANAME, PHONE, TEL_NO, PHONE2
|
||||
FROM CD_PERSON
|
||||
WHERE CUSCODE = ?
|
||||
""", cus_code)
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
return jsonify({'success': False, 'mileage': None})
|
||||
|
||||
name, phone1, phone2, phone3 = row
|
||||
phone = phone1 or phone2 or phone3 or ''
|
||||
phone_digits = ''.join(c for c in phone if c.isdigit())
|
||||
last4 = phone_digits[-4:] if len(phone_digits) >= 4 else ''
|
||||
|
||||
if not name or not last4:
|
||||
return jsonify({'success': False, 'mileage': None})
|
||||
|
||||
# 2. SQLite에서 이름+전화뒤4자리로 매칭
|
||||
sqlite_conn = db_manager.get_sqlite_connection()
|
||||
sqlite_cursor = sqlite_conn.cursor()
|
||||
|
||||
sqlite_cursor.execute("""
|
||||
SELECT nickname, phone, mileage_balance
|
||||
FROM users
|
||||
""")
|
||||
|
||||
for user in sqlite_cursor.fetchall():
|
||||
user_phone = ''.join(c for c in (user['phone'] or '') if c.isdigit())
|
||||
user_last4 = user_phone[-4:] if len(user_phone) >= 4 else ''
|
||||
|
||||
if user['nickname'] == name and user_last4 == last4:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'mileage': user['mileage_balance'] or 0,
|
||||
'name': name
|
||||
})
|
||||
|
||||
return jsonify({'success': False, 'mileage': None})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"마일리지 조회 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
finally:
|
||||
if mssql_conn:
|
||||
mssql_conn.close()
|
||||
|
||||
|
||||
@app.route('/api/customers/search')
|
||||
def api_customers_search():
|
||||
"""
|
||||
고객 검색 API (CD_PERSON + 최근 조제/구매 활동)
|
||||
- name: 검색할 이름
|
||||
- 결과: 최근 활동순 정렬, 생년월일 포함
|
||||
"""
|
||||
name = request.args.get('name', '').strip()
|
||||
if not name or len(name) < 2:
|
||||
return jsonify({'success': False, 'error': '이름을 2자 이상 입력하세요.'}), 400
|
||||
|
||||
mssql_conn = None
|
||||
try:
|
||||
mssql_engine = db_manager.get_engine('PM_BASE')
|
||||
mssql_conn = mssql_engine.raw_connection()
|
||||
cursor = mssql_conn.cursor()
|
||||
|
||||
# CD_PERSON에서 이름으로 검색 + 최근 조제/구매일 조인
|
||||
cursor.execute("""
|
||||
SELECT DISTINCT
|
||||
p.CUSCODE,
|
||||
p.PANAME,
|
||||
LEFT(p.PANUM, 6) AS birth_date,
|
||||
p.PHONE,
|
||||
(SELECT MAX(Indate) FROM PM_PRES.dbo.PS_main WHERE CusCode = p.CUSCODE) AS last_rx,
|
||||
(SELECT MAX(InsertTime) FROM PM_PRES.dbo.SALE_MAIN WHERE SL_CD_custom = p.CUSCODE) AS last_sale
|
||||
FROM CD_PERSON p
|
||||
WHERE p.PANAME LIKE ?
|
||||
ORDER BY p.PANAME
|
||||
""", f'%{name}%')
|
||||
|
||||
rows = cursor.fetchall()
|
||||
results = []
|
||||
|
||||
for row in rows:
|
||||
cus_code, pa_name, birth, phone, last_rx, last_sale = row
|
||||
|
||||
# 최근 활동일 계산
|
||||
last_activity = None
|
||||
activity_type = None
|
||||
|
||||
if last_rx and last_sale:
|
||||
# 둘 다 있으면 더 최근 것
|
||||
rx_date = datetime.strptime(last_rx, '%Y%m%d') if isinstance(last_rx, str) else last_rx
|
||||
if isinstance(last_sale, datetime):
|
||||
if rx_date > last_sale:
|
||||
last_activity = rx_date
|
||||
activity_type = '조제'
|
||||
else:
|
||||
last_activity = last_sale
|
||||
activity_type = '구매'
|
||||
else:
|
||||
last_activity = rx_date
|
||||
activity_type = '조제'
|
||||
elif last_rx:
|
||||
last_activity = datetime.strptime(last_rx, '%Y%m%d') if isinstance(last_rx, str) else last_rx
|
||||
activity_type = '조제'
|
||||
elif last_sale:
|
||||
last_activity = last_sale
|
||||
activity_type = '구매'
|
||||
|
||||
# 며칠 전 계산
|
||||
days_ago = None
|
||||
if last_activity:
|
||||
if isinstance(last_activity, datetime):
|
||||
days_ago = (datetime.now() - last_activity).days
|
||||
else:
|
||||
days_ago = (datetime.now() - datetime.strptime(str(last_activity)[:8], '%Y%m%d')).days
|
||||
|
||||
results.append({
|
||||
'cus_code': cus_code,
|
||||
'name': pa_name,
|
||||
'birth': birth if birth else '',
|
||||
'phone': phone if phone else '',
|
||||
'activity_type': activity_type,
|
||||
'days_ago': days_ago,
|
||||
'last_activity': last_activity.strftime('%Y-%m-%d') if last_activity else None
|
||||
})
|
||||
|
||||
# 최근 활동순 정렬 (활동 있는 것 먼저, 그 중 최근 것 먼저)
|
||||
results.sort(key=lambda x: (x['days_ago'] is None, x['days_ago'] if x['days_ago'] is not None else 9999))
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'results': results[:50] # 최대 50개
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"고객 검색 오류: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
finally:
|
||||
if mssql_conn:
|
||||
mssql_conn.close()
|
||||
|
||||
|
||||
@app.route('/api/pos-live/<order_no>/customer', methods=['PUT'])
|
||||
def api_pos_live_update_customer(order_no):
|
||||
"""
|
||||
판매 건의 고객 정보 업데이트 API
|
||||
- cus_code: 고객 코드 (CD_PERSON.CUSCODE)
|
||||
- cus_name: 고객 이름
|
||||
"""
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({'success': False, 'error': 'JSON 데이터 필요'}), 400
|
||||
|
||||
cus_code = data.get('cus_code', '').strip()
|
||||
cus_name = data.get('cus_name', '').strip()
|
||||
|
||||
if not cus_code or not cus_name:
|
||||
return jsonify({'success': False, 'error': '고객 코드와 이름 필요'}), 400
|
||||
|
||||
mssql_conn = None
|
||||
try:
|
||||
mssql_engine = db_manager.get_engine('PM_PRES')
|
||||
mssql_conn = mssql_engine.raw_connection()
|
||||
cursor = mssql_conn.cursor()
|
||||
|
||||
# SALE_MAIN 업데이트
|
||||
cursor.execute("""
|
||||
UPDATE SALE_MAIN
|
||||
SET SL_CD_custom = ?, SL_NM_custom = ?
|
||||
WHERE SL_NO_order = ?
|
||||
""", cus_code, cus_name, order_no)
|
||||
|
||||
mssql_conn.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'고객 정보가 {cus_name}({cus_code})으로 업데이트되었습니다.'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"고객 정보 업데이트 오류: {e}")
|
||||
if mssql_conn:
|
||||
mssql_conn.rollback()
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
finally:
|
||||
if mssql_conn:
|
||||
mssql_conn.close()
|
||||
|
||||
|
||||
@app.route('/api/admin/user-mileage/<phone>')
|
||||
def api_admin_user_mileage(phone):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user