chore(version): 버전 8.58 업데이트 및 문자 이력 고객정보 저장 기능 추가
수동 일반 발주의 이름·연락처·주소를 문자 이력에 함께 저장하고 최근 전송 메시지 클릭 시 다시 불러오도록 개선 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -214,12 +214,20 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
# Globals
|
||||
SMS_HISTORY_FILE = 'sms_history.dat'
|
||||
SMS_HISTORY_HEADERS = ["일시", "주문유형", "입금액", "주문아이템", "메시지 내용", "이름", "연락처", "주소"]
|
||||
GSPREAD_CRED_FILE = 'manual-ordering.json'
|
||||
MANUAL_SPREADSHEET_ID = '1QxFtjDurPPZd8NmtXBy4XEUIzkf93aXD5Mlx5kB33xw'
|
||||
SETTINGS_SPREADSHEET_ID = '1k7SNEdIaRtGXzQNdJSQAiNSDRwzOePDRTLHY_mNCO8M'
|
||||
MANUAL_HISTORY_FILE = 'manual_history.dat'
|
||||
|
||||
# ----- 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):
|
||||
global _CONFIG_ROWS_CACHE, _CONFIG_ROWS_CACHE_TIME
|
||||
|
||||
@@ -508,7 +516,7 @@ def get_sms_history_worksheet():
|
||||
worksheet_sms_history_global = ss.worksheet("문자내역")
|
||||
except gspread.exceptions.WorksheetNotFound:
|
||||
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
|
||||
|
||||
def get_manual_history_worksheet():
|
||||
@@ -647,6 +655,16 @@ async def download_and_clear_sheet():
|
||||
@app.get("/api/sms/history")
|
||||
async def get_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):
|
||||
with open(SMS_HISTORY_FILE, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
@@ -657,7 +675,7 @@ async def get_history():
|
||||
local_history[item["timestamp"]] = item
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
history = []
|
||||
try:
|
||||
ws = get_sms_history_worksheet()
|
||||
@@ -667,23 +685,30 @@ async def get_history():
|
||||
if not row or not row[0].strip():
|
||||
continue
|
||||
timestamp = row[0]
|
||||
|
||||
if timestamp in local_history:
|
||||
history.append(local_history[timestamp])
|
||||
else:
|
||||
order_type = row[1] if len(row) > 1 else ""
|
||||
deposit_amt = row[2] if len(row) > 2 else ""
|
||||
items_str = row[3] if len(row) > 3 else ""
|
||||
preview_text = row[4] if len(row) > 4 else ""
|
||||
preview_text = preview_text.replace(" ", "\n")
|
||||
|
||||
history.append({
|
||||
"timestamp": timestamp,
|
||||
"orderType": order_type,
|
||||
"deposit_amount_str": deposit_amt,
|
||||
"items_str": items_str,
|
||||
"previewText": preview_text
|
||||
})
|
||||
|
||||
order_type = row[1] if len(row) > 1 else ""
|
||||
deposit_amt = row[2] if len(row) > 2 else ""
|
||||
items_str = row[3] if len(row) > 3 else ""
|
||||
preview_text = row[4] if len(row) > 4 else ""
|
||||
cust_name = row[5] if len(row) > 5 else ""
|
||||
cust_phone = row[6] if len(row) > 6 else ""
|
||||
cust_address = row[7] if len(row) > 7 else ""
|
||||
preview_text = preview_text.replace(" ", "\n")
|
||||
|
||||
sheet_item = {
|
||||
"timestamp": timestamp,
|
||||
"orderType": order_type,
|
||||
"deposit_amount_str": deposit_amt,
|
||||
"items_str": items_str,
|
||||
"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
|
||||
except Exception as e:
|
||||
print(f"구글 시트에서 기록을 읽어오는 중 에러: {e}")
|
||||
@@ -710,15 +735,22 @@ async def add_history(data: dict):
|
||||
|
||||
# 행 높이가 길어지지 않도록 줄바꿈을 띄어쓰기로 변경
|
||||
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 = [
|
||||
data.get("timestamp", ""),
|
||||
data.get("orderType", ""),
|
||||
data.get("deposit_amount_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:
|
||||
print(f"구글 시트 문자내역 기록 실패: {e}")
|
||||
|
||||
|
||||
+51
-7
@@ -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 = {
|
||||
timestamp: stamp,
|
||||
deposit_amount_str: finalPayable.toLocaleString() + "원",
|
||||
custName: document.getElementById('cust-name').value.trim(),
|
||||
custPhone: document.getElementById('cust-phone').value.trim(),
|
||||
custAddress: document.getElementById('cust-address').value.trim(),
|
||||
custName: custName,
|
||||
custPhone: custPhone,
|
||||
custAddress: custAddress,
|
||||
customer_name: custName,
|
||||
phone: custPhone,
|
||||
address: custAddress,
|
||||
orderType: document.querySelector('input[name="order-type"]:checked').value,
|
||||
noLid: document.getElementById('chk-no-lid').checked,
|
||||
items: savedItems,
|
||||
@@ -1224,7 +1231,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
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
|
||||
// ============================================
|
||||
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() {
|
||||
const u = document.getElementById('sms-history-list');
|
||||
u.innerHTML = '';
|
||||
@@ -1338,9 +1378,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
document.getElementById('btn-reset').click();
|
||||
|
||||
if (d.custName !== undefined) document.getElementById('cust-name').value = d.custName;
|
||||
if (d.custPhone !== undefined) document.getElementById('cust-phone').value = d.custPhone;
|
||||
if (d.custAddress !== undefined) document.getElementById('cust-address').value = d.custAddress;
|
||||
const cachedCustomer = readSmsCustomerCache()[d.timestamp] || {};
|
||||
const savedCustName = firstNonEmpty(d.custName, d.customer_name, cachedCustomer.custName, cachedCustomer.customer_name);
|
||||
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) {
|
||||
const rb = document.querySelector(`input[name="order-type"][value="${d.orderType}"]`);
|
||||
if (rb) rb.checked = true;
|
||||
|
||||
@@ -46,10 +46,10 @@
|
||||
<h1>No.1 King</h1>
|
||||
<p>CS 통합 관리</p>
|
||||
<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 style="color: #a0aec0; font-size: 0.75rem; margin-top: 4px; font-weight: normal;">
|
||||
(2026.05.26)
|
||||
(2026.06.01)
|
||||
</div>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
[
|
||||
{
|
||||
"version": "v8.58",
|
||||
"date": "2026.06.01",
|
||||
"sections": [
|
||||
{
|
||||
"items": [
|
||||
"계산 및 내용 복사 시 수동 일반 발주의 이름, 연락처, 주소를 문자 이력에 함께 저장하고 최근 전송 메시지 클릭 시 다시 불러오도록 개선"
|
||||
],
|
||||
"type": "modified"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v8.57",
|
||||
"date": "2026.05.26",
|
||||
|
||||
Reference in New Issue
Block a user