chore(version): 버전 8.58 업데이트 및 문자 이력 고객정보 저장 기능 추가

수동 일반 발주의 이름·연락처·주소를 문자 이력에 함께 저장하고
최근 전송 메시지 클릭 시 다시 불러오도록 개선

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 11:37:44 +09:00
parent 2f73e605aa
commit 7afe90646b
4 changed files with 119 additions and 31 deletions
+54 -22
View File
@@ -214,12 +214,20 @@ templates = Jinja2Templates(directory="templates")
# Globals # Globals
SMS_HISTORY_FILE = 'sms_history.dat' SMS_HISTORY_FILE = 'sms_history.dat'
SMS_HISTORY_HEADERS = ["일시", "주문유형", "입금액", "주문아이템", "메시지 내용", "이름", "연락처", "주소"]
GSPREAD_CRED_FILE = 'manual-ordering.json' GSPREAD_CRED_FILE = 'manual-ordering.json'
MANUAL_SPREADSHEET_ID = '1QxFtjDurPPZd8NmtXBy4XEUIzkf93aXD5Mlx5kB33xw' MANUAL_SPREADSHEET_ID = '1QxFtjDurPPZd8NmtXBy4XEUIzkf93aXD5Mlx5kB33xw'
SETTINGS_SPREADSHEET_ID = '1k7SNEdIaRtGXzQNdJSQAiNSDRwzOePDRTLHY_mNCO8M' SETTINGS_SPREADSHEET_ID = '1k7SNEdIaRtGXzQNdJSQAiNSDRwzOePDRTLHY_mNCO8M'
MANUAL_HISTORY_FILE = 'manual_history.dat' MANUAL_HISTORY_FILE = 'manual_history.dat'
# ----- Utility Functions ----- # ----- Utility Functions -----
def first_nonempty(data: dict, *keys: str) -> str:
for key in keys:
value = data.get(key)
if value is not None and str(value).strip():
return str(value)
return ""
def read_config(force_refresh=False): def read_config(force_refresh=False):
global _CONFIG_ROWS_CACHE, _CONFIG_ROWS_CACHE_TIME global _CONFIG_ROWS_CACHE, _CONFIG_ROWS_CACHE_TIME
@@ -508,7 +516,7 @@ def get_sms_history_worksheet():
worksheet_sms_history_global = ss.worksheet("문자내역") worksheet_sms_history_global = ss.worksheet("문자내역")
except gspread.exceptions.WorksheetNotFound: except gspread.exceptions.WorksheetNotFound:
worksheet_sms_history_global = ss.add_worksheet(title="문자내역", rows=1000, cols=10) worksheet_sms_history_global = ss.add_worksheet(title="문자내역", rows=1000, cols=10)
worksheet_sms_history_global.append_row(["일시", "주문유형", "입금액", "주문아이템", "메시지 내용"]) worksheet_sms_history_global.append_row(SMS_HISTORY_HEADERS)
return worksheet_sms_history_global return worksheet_sms_history_global
def get_manual_history_worksheet(): def get_manual_history_worksheet():
@@ -647,6 +655,16 @@ async def download_and_clear_sheet():
@app.get("/api/sms/history") @app.get("/api/sms/history")
async def get_history(): async def get_history():
local_history = {} local_history = {}
def merge_history_item(sheet_item, local_item):
if not local_item:
return sheet_item
merged = dict(sheet_item)
merged.update(local_item)
for key in ("custName", "custPhone", "custAddress", "customer_name", "phone", "address"):
if not merged.get(key) and sheet_item.get(key):
merged[key] = sheet_item[key]
return merged
if os.path.exists(SMS_HISTORY_FILE): if os.path.exists(SMS_HISTORY_FILE):
with open(SMS_HISTORY_FILE, 'r', encoding='utf-8') as f: with open(SMS_HISTORY_FILE, 'r', encoding='utf-8') as f:
for line in f: for line in f:
@@ -657,7 +675,7 @@ async def get_history():
local_history[item["timestamp"]] = item local_history[item["timestamp"]] = item
except: except:
pass pass
history = [] history = []
try: try:
ws = get_sms_history_worksheet() ws = get_sms_history_worksheet()
@@ -667,23 +685,30 @@ async def get_history():
if not row or not row[0].strip(): if not row or not row[0].strip():
continue continue
timestamp = row[0] timestamp = row[0]
if timestamp in local_history: order_type = row[1] if len(row) > 1 else ""
history.append(local_history[timestamp]) deposit_amt = row[2] if len(row) > 2 else ""
else: items_str = row[3] if len(row) > 3 else ""
order_type = row[1] if len(row) > 1 else "" preview_text = row[4] if len(row) > 4 else ""
deposit_amt = row[2] if len(row) > 2 else "" cust_name = row[5] if len(row) > 5 else ""
items_str = row[3] if len(row) > 3 else "" cust_phone = row[6] if len(row) > 6 else ""
preview_text = row[4] if len(row) > 4 else "" cust_address = row[7] if len(row) > 7 else ""
preview_text = preview_text.replace(" ", "\n") preview_text = preview_text.replace(" ", "\n")
history.append({ sheet_item = {
"timestamp": timestamp, "timestamp": timestamp,
"orderType": order_type, "orderType": order_type,
"deposit_amount_str": deposit_amt, "deposit_amount_str": deposit_amt,
"items_str": items_str, "items_str": items_str,
"previewText": preview_text "previewText": preview_text,
}) "custName": cust_name,
"custPhone": cust_phone,
"custAddress": cust_address,
"customer_name": cust_name,
"phone": cust_phone,
"address": cust_address
}
history.append(merge_history_item(sheet_item, local_history.get(timestamp)))
return history return history
except Exception as e: except Exception as e:
print(f"구글 시트에서 기록을 읽어오는 중 에러: {e}") print(f"구글 시트에서 기록을 읽어오는 중 에러: {e}")
@@ -710,15 +735,22 @@ async def add_history(data: dict):
# 행 높이가 길어지지 않도록 줄바꿈을 띄어쓰기로 변경 # 행 높이가 길어지지 않도록 줄바꿈을 띄어쓰기로 변경
preview_text = data.get("previewText", "").replace("\n", " ").replace("\r", "") preview_text = data.get("previewText", "").replace("\n", " ").replace("\r", "")
customer_name = first_nonempty(data, "custName", "customer_name", "customerName", "name")
customer_phone = first_nonempty(data, "custPhone", "phone", "customer_phone", "customerPhone")
customer_address = first_nonempty(data, "custAddress", "address", "customer_address", "customerAddress")
row = [ row = [
data.get("timestamp", ""), data.get("timestamp", ""),
data.get("orderType", ""), data.get("orderType", ""),
data.get("deposit_amount_str", ""), data.get("deposit_amount_str", ""),
items_str, items_str,
preview_text preview_text,
customer_name,
customer_phone,
customer_address
] ]
ws.append_row(row, value_input_option='USER_ENTERED') next_row = len(ws.col_values(1)) + 1
ws.update(values=[row], range_name=f"A{next_row}:H{next_row}", value_input_option='USER_ENTERED')
except Exception as e: except Exception as e:
print(f"구글 시트 문자내역 기록 실패: {e}") print(f"구글 시트 문자내역 기록 실패: {e}")
+51 -7
View File
@@ -1201,12 +1201,19 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
const custName = document.getElementById('cust-name').value.trim();
const custPhone = document.getElementById('cust-phone').value.trim();
const custAddress = document.getElementById('cust-address').value.trim();
const histData = { const histData = {
timestamp: stamp, timestamp: stamp,
deposit_amount_str: finalPayable.toLocaleString() + "원", deposit_amount_str: finalPayable.toLocaleString() + "원",
custName: document.getElementById('cust-name').value.trim(), custName: custName,
custPhone: document.getElementById('cust-phone').value.trim(), custPhone: custPhone,
custAddress: document.getElementById('cust-address').value.trim(), custAddress: custAddress,
customer_name: custName,
phone: custPhone,
address: custAddress,
orderType: document.querySelector('input[name="order-type"]:checked').value, orderType: document.querySelector('input[name="order-type"]:checked').value,
noLid: document.getElementById('chk-no-lid').checked, noLid: document.getElementById('chk-no-lid').checked,
items: savedItems, items: savedItems,
@@ -1224,7 +1231,14 @@ document.addEventListener('DOMContentLoaded', () => {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(histData) body: JSON.stringify(histData)
}).then(() => loadSmsHistory()); }).then(() => {
writeSmsCustomerCache(stamp, {
custName: custName,
custPhone: custPhone,
custAddress: custAddress
});
loadSmsHistory();
});
}); });
@@ -1232,6 +1246,32 @@ document.addEventListener('DOMContentLoaded', () => {
// ============================================ // ============================================
// SMS HISTORY // SMS HISTORY
// ============================================ // ============================================
const SMS_CUSTOMER_CACHE_KEY = 'smsCustomerHistoryByTimestamp';
function readSmsCustomerCache() {
try {
return JSON.parse(localStorage.getItem(SMS_CUSTOMER_CACHE_KEY) || '{}');
} catch (e) {
return {};
}
}
function writeSmsCustomerCache(timestamp, customer) {
if (!timestamp) return;
const cache = readSmsCustomerCache();
cache[timestamp] = customer;
localStorage.setItem(SMS_CUSTOMER_CACHE_KEY, JSON.stringify(cache));
}
function firstNonEmpty(...values) {
for (const value of values) {
if (value !== undefined && value !== null && String(value).trim() !== '') {
return value;
}
}
return '';
}
function loadSmsHistory() { function loadSmsHistory() {
const u = document.getElementById('sms-history-list'); const u = document.getElementById('sms-history-list');
u.innerHTML = ''; u.innerHTML = '';
@@ -1338,9 +1378,13 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('btn-reset').click(); document.getElementById('btn-reset').click();
if (d.custName !== undefined) document.getElementById('cust-name').value = d.custName; const cachedCustomer = readSmsCustomerCache()[d.timestamp] || {};
if (d.custPhone !== undefined) document.getElementById('cust-phone').value = d.custPhone; const savedCustName = firstNonEmpty(d.custName, d.customer_name, cachedCustomer.custName, cachedCustomer.customer_name);
if (d.custAddress !== undefined) document.getElementById('cust-address').value = d.custAddress; const savedCustPhone = firstNonEmpty(d.custPhone, d.phone, cachedCustomer.custPhone, cachedCustomer.phone);
const savedCustAddress = firstNonEmpty(d.custAddress, d.address, cachedCustomer.custAddress, cachedCustomer.address);
document.getElementById('cust-name').value = savedCustName;
document.getElementById('cust-phone').value = savedCustPhone;
document.getElementById('cust-address').value = savedCustAddress;
if (d.orderType !== undefined) { if (d.orderType !== undefined) {
const rb = document.querySelector(`input[name="order-type"][value="${d.orderType}"]`); const rb = document.querySelector(`input[name="order-type"][value="${d.orderType}"]`);
if (rb) rb.checked = true; if (rb) rb.checked = true;
+2 -2
View File
@@ -46,10 +46,10 @@
<h1>No.1 King</h1> <h1>No.1 King</h1>
<p>CS 통합 관리</p> <p>CS 통합 관리</p>
<div id="app-version-text" style="color: #a0aec0; font-size: 0.95rem; margin-top: 4px; font-weight: normal;"> <div id="app-version-text" style="color: #a0aec0; font-size: 0.95rem; margin-top: 4px; font-weight: normal;">
Ver 8.57 Ver 8.58
</div> </div>
<div style="color: #a0aec0; font-size: 0.75rem; margin-top: 4px; font-weight: normal;"> <div style="color: #a0aec0; font-size: 0.75rem; margin-top: 4px; font-weight: normal;">
(2026.05.26) (2026.06.01)
</div> </div>
</div> </div>
<ul class="nav-links"> <ul class="nav-links">
+12
View File
@@ -1,4 +1,16 @@
[ [
{
"version": "v8.58",
"date": "2026.06.01",
"sections": [
{
"items": [
"계산 및 내용 복사 시 수동 일반 발주의 이름, 연락처, 주소를 문자 이력에 함께 저장하고 최근 전송 메시지 클릭 시 다시 불러오도록 개선"
],
"type": "modified"
}
]
},
{ {
"version": "v8.57", "version": "v8.57",
"date": "2026.05.26", "date": "2026.05.26",