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:
thug0bin
2026-03-11 23:22:57 +09:00
parent 1deba9e631
commit 9f10f8fdbb
2 changed files with 544 additions and 7 deletions

View File

@@ -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):
"""