initialCommit

This commit is contained in:
9700X_PC 2025-02-10 22:15:13 +09:00
commit 1f1fce394e
14 changed files with 1475 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
__pycache__/
Lib/
Scripts/
Include/
src/__pycache__
*.pyc
pyvenv.cfg

0
app.log Normal file
View File

60
gui/help_dialog.py Normal file
View File

@ -0,0 +1,60 @@
# gui/help_dialog.py
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QTextEdit, QPushButton
class HelpDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("프로그램 사용설명서")
self.resize(600, 400)
self.setup_ui()
self.apply_styles()
def setup_ui(self):
layout = QVBoxLayout(self)
help_text = """
<h2>주문 알림 SMS 전송 프로그램 사용설명서</h2>
<p> 프로그램은 국내마켓 주문 접수 중국의 타오바오 주문, 배송대행지 주문 단계별로 발생하는 트래킹번호를
기반으로 고객에게 SMS 알림을 전송합니다.</p>
<h3>주요 기능</h3>
<ul>
<li>주문정보 입력: 고객 정보, 주문 마켓, 트래킹번호, CS 메모 등을 입력합니다.</li>
<li>템플릿 관리: 단계별 템플릿을 저장하고 불러와 주문 정보에 자동으로 적용할 있습니다.</li>
<li>실시간 로그: 프로그램 실행 발생하는 로그를 확인할 있습니다.</li>
</ul>
<h3>사용 방법</h3>
<ol>
<li>메인 창에서 "주문정보 입력" 버튼을 클릭하여 주문 정보를 입력합니다.</li>
<li>입력 완료 "입력완료", "입력완료 및 1단계 문자발송", "임시저장" 등의 버튼을 사용합니다.</li>
<li>템플릿 관리 창에서 SMS 템플릿을 편집하고 저장할 있습니다.</li>
<li>메뉴의 도움말을 통해 사용설명서를 언제든지 확인할 있습니다.</li>
</ol>
"""
self.text_edit = QTextEdit()
self.text_edit.setReadOnly(True)
self.text_edit.setHtml(help_text)
layout.addWidget(self.text_edit)
close_button = QPushButton("닫기")
close_button.clicked.connect(self.accept)
layout.addWidget(close_button)
def apply_styles(self):
style = """
QDialog {
background-color: #ffffff;
}
QTextEdit {
border: none;
font-size: 13px;
}
QPushButton {
background-color: #1976D2;
color: white;
border-radius: 4px;
padding: 6px 12px;
}
QPushButton:hover {
background-color: #1565C0;
}
"""
self.setStyleSheet(style)

212
gui/main_window.py Normal file
View File

@ -0,0 +1,212 @@
# gui/main_window.py
from PySide6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QTextEdit, QMenuBar, QMenu, QLabel, QTableWidget, QTableWidgetItem, QMessageBox)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt, Slot
from gui.order_input_dialog import OrderInputDialog
from gui.template_management_dialog import TemplateManagementDialog
from gui.help_dialog import HelpDialog
from gui.settings_dialog import SettingsDialog
from src.database_module import DatabaseManager
# 메인 창 클래스
class MainWindow(QMainWindow):
def __init__(self, logger):
super().__init__()
self.logger = logger
self.db_manager = DatabaseManager() # SQLite, SQLAlchemy 기반 DB 관리자
self.setWindowTitle("주문 알림 SMS 전송 프로그램")
self.resize(900, 700)
self.setup_menu()
self.setup_ui()
self.apply_styles()
self.refresh_order_list()
def setup_menu(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("파일")
settings_action = QAction("사용자 설정", self)
settings_action.triggered.connect(self.open_settings_dialog)
file_menu.addAction(settings_action)
exit_action = QAction("종료", self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
help_menu = menu_bar.addMenu("도움말")
usage_action = QAction("프로그램 사용설명서", self)
usage_action.triggered.connect(self.show_help)
help_menu.addAction(usage_action)
def setup_ui(self):
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 상단 버튼 영역
button_layout = QHBoxLayout()
self.order_input_button = QPushButton("주문정보 입력")
self.order_input_button.setToolTip("주문 정보를 입력하여 문자 전송 준비")
self.order_input_button.clicked.connect(self.open_order_input_dialog)
button_layout.addWidget(self.order_input_button)
self.template_button = QPushButton("템플릿 관리")
self.template_button.setToolTip("문자 템플릿을 저장, 불러오기 및 수정")
self.template_button.clicked.connect(self.open_template_management_dialog)
button_layout.addWidget(self.template_button)
main_layout.addLayout(button_layout)
# 주문 목록 테이블
self.order_table = QTableWidget()
self.order_table.setColumnCount(6)
self.order_table.setHorizontalHeaderLabels(["ID", "상품명", "고객명", "전화번호", "현재단계", "SMS발송여부", "최종 업데이트"])
self.order_table.setToolTip("현재 진행 중인 주문 목록을 표시합니다.")
main_layout.addWidget(QLabel("주문 목록:"))
main_layout.addWidget(self.order_table)
# 로그 출력 영역
self.log_display = QTextEdit()
self.log_display.setReadOnly(True)
self.log_display.setToolTip("프로그램 로그가 표시됩니다.")
main_layout.addWidget(QLabel("로그:"))
main_layout.addWidget(self.log_display)
self.logger.log_signal.connect(self.append_log)
self.order_table.itemChanged.connect(self.item_changed_slot)
@Slot(str)
def append_log(self, message: str):
self.log_display.append(message)
def open_order_input_dialog(self):
dialog = OrderInputDialog(self.logger, parent=self)
if dialog.exec():
# 주문 입력 완료 후 주문 목록 새로고침
self.refresh_order_list()
def open_template_management_dialog(self):
dialog = TemplateManagementDialog(self.logger, parent=self)
dialog.exec()
def open_settings_dialog(self):
dialog = SettingsDialog(self)
dialog.exec()
def show_help(self):
dialog = HelpDialog(parent=self)
dialog.exec()
def refresh_order_list(self):
orders = self.db_manager.get_all_orders()
self.order_table.blockSignals(True) # 초기화 시 itemChanged 시그널 방지
self.order_table.setRowCount(len(orders))
for row, order in enumerate(orders):
# 각 셀을 편집 가능하게 설정
id_item = QTableWidgetItem(str(order.id))
id_item.setFlags(id_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 0, id_item)
name_item = QTableWidgetItem(order.customer_name or "")
name_item.setFlags(name_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 1, name_item)
product_item = QTableWidgetItem(order.product_name or "")
product_item.setFlags(product_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 2, product_item)
phone_item = QTableWidgetItem(order.customer_phone)
phone_item.setFlags(phone_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 3, phone_item)
step_item = QTableWidgetItem(str(order.order_step))
step_item.setFlags(step_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 4, step_item)
sms_item = QTableWidgetItem("전송 완료" if order.domestic_tracking else "미전송")
sms_item.setFlags(sms_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 5, sms_item)
updated_at_str = order.updated_at.strftime("%Y-%m-%d %H:%M:%S") if order.updated_at else ""
update_item = QTableWidgetItem(updated_at_str)
update_item.setFlags(update_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 6, update_item)
self.order_table.blockSignals(False)
@Slot()
def item_changed_slot(self):
# 사용자가 셀을 수정한 경우, 수정 내용을 DB에 업데이트하고 확인 메시지를 보여줌
row = self.order_table.currentRow()
if row < 0:
return
order_id = int(self.order_table.item(row, 0).text())
customer_name = self.order_table.item(row, 1).text()
product_name = self.order_table.item(row, 2).text()
customer_phone = self.order_table.item(row, 3).text()
# 기타 수정 가능한 필드는 필요에 따라 추가
# 수정 내용을 데이터베이스에 업데이트 (예시)
updated_order = self.db_manager.update_order(order_id,
customer_name=customer_name,
product_name=product_name,
customer_phone=customer_phone)
# 확인 메시지 표시
QMessageBox.information(self, "저장 확인", f"주문서(ID {order_id})가 수정되었습니다.")
self.logger.log(f"주문서(ID {order_id}) 수정: 고객명={customer_name}, 상품명={product_name}, 전화번호={customer_phone}", level=1)
def open_settings_dialog(self):
from gui.settings_dialog import SettingsDialog
dialog = SettingsDialog(self)
dialog.exec()
def show_help(self):
from gui.help_dialog import HelpDialog
dialog = HelpDialog(parent=self)
dialog.exec()
def apply_styles(self):
style = """
QMainWindow {
background-color: #f7f7f7;
}
QPushButton {
background-color: #4CAF50;
color: white;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
}
QPushButton:hover {
background-color: #45a049;
}
QTextEdit {
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 12px;
}
QTableWidget {
background-color: white;
border: 1px solid #ccc;
}
QMenuBar {
background-color: #333;
color: white;
}
QMenuBar::item {
background-color: #333;
padding: 4px 10px;
}
QMenuBar::item:selected {
background-color: #555;
}
QMenu {
background-color: #444;
color: white;
}
QMenu::item:selected {
background-color: #666;
}
"""
self.setStyleSheet(style)

145
gui/order_input_dialog.py Normal file
View File

@ -0,0 +1,145 @@
# gui/order_input_dialog.py (일부 발췌)
from PySide6.QtWidgets import QDialog, QFormLayout, QLineEdit, QComboBox, QPushButton, QHBoxLayout, QVBoxLayout, QLabel
from PySide6.QtCore import QSettings
class OrderInputDialog(QDialog):
def __init__(self, logger, parent=None):
super().__init__(parent)
self.logger = logger
self.setWindowTitle("주문정보 입력")
self.setup_ui()
self.apply_styles()
order_data = {
"customer_name": self.customer_name_edit.text().strip(),
"product_name": self.product_name_edit.text().strip(), # 추가
"order_market": self.order_market_edit.text().strip(),
"customer_phone": self.customer_phone_edit.text().strip(),
"shop_name": self.shop_combo.currentText(),
"taobao_tracking": self.taobao_tracking_edit.text().strip(),
"delivery_agent": self.delivery_agent_edit.text().strip(),
"pre_carrier": self.pre_carrier_edit.text().strip(),
"domestic_tracking": self.tracking_edit.text().strip(),
"freight_tracking": self.freight_tracking_edit.text().strip(),
"cs_memo1": self.cs_memo1_edit.text().strip(),
"cs_memo2": self.cs_memo2_edit.text().strip(),
"order_step": None # '선택된 단계번호", # 예: 1 ~ 5
# 기타 필요한 필드...
}
def setup_ui(self):
layout = QVBoxLayout(self)
form_layout = QFormLayout()
# 샵 이름 선택 ComboBox 추가
self.shop_combo = QComboBox()
self.shop_combo.setToolTip("주문서에 사용할 샵 이름을 선택하세요")
settings = QSettings("MyCompany", "MySMSApp")
shop_names = settings.value("shop_names", [])
if not isinstance(shop_names, list):
shop_names = [shop_names] if shop_names else []
self.shop_combo.addItems(shop_names)
form_layout.addRow("샵 이름:", self.shop_combo)
self.customer_name_edit = QLineEdit()
self.customer_name_edit.setPlaceholderText("고객 이름 입력")
self.customer_name_edit.setToolTip("고객의 이름을 입력하세요")
form_layout.addRow("고객 이름:", self.customer_name_edit)
self.product_name_edit = QLineEdit()
self.product_name_edit.setPlaceholderText("상품명")
self.product_name_edit.setToolTip("상품명을 입력하세요")
form_layout.addRow("상품명:", self.product_name_edit)
self.order_market_edit = QLineEdit()
self.order_market_edit.setPlaceholderText("주문 마켓 입력 (예: 쿠팡, 11번가 등)")
self.order_market_edit.setToolTip("주문이 발생한 국내 마켓을 입력하세요")
form_layout.addRow("주문 마켓:", self.order_market_edit)
self.customer_phone_edit = QLineEdit()
self.customer_phone_edit.setPlaceholderText("010-1234-5678")
self.customer_phone_edit.setToolTip("고객 전화번호를 입력하세요")
form_layout.addRow("고객 전화번호:", self.customer_phone_edit)
self.taobao_tracking_edit = QLineEdit()
self.taobao_tracking_edit.setPlaceholderText("타오바오 트래킹 번호 입력")
self.taobao_tracking_edit.setToolTip("타오바오에서 발행된 트래킹 번호")
form_layout.addRow("타오바오 트래킹:", self.taobao_tracking_edit)
self.delivery_agent_edit = QLineEdit()
self.delivery_agent_edit.setPlaceholderText("배대지 이름 입력")
self.delivery_agent_edit.setToolTip("배송대행지(배대지)의 이름을 입력하세요")
form_layout.addRow("배대지 이름:", self.delivery_agent_edit)
self.pre_carrier_edit = QLineEdit()
self.pre_carrier_edit.setPlaceholderText("선송장 택배사 입력")
self.pre_carrier_edit.setToolTip("주문 발송 시 사용한 택배사를 입력하세요")
form_layout.addRow("선송장 택배사:", self.pre_carrier_edit)
self.tracking_edit = QLineEdit()
self.tracking_edit.setPlaceholderText("배송대행지 주문 트래킹 번호 입력")
self.tracking_edit.setToolTip("배송대행지에서 발행한 국내 트래킹 번호")
form_layout.addRow("국내 트래킹번호:", self.tracking_edit)
self.freight_carrier_edit = QLineEdit()
self.freight_carrier_edit.setPlaceholderText("화물택배사 입력")
self.freight_carrier_edit.setToolTip("물품이 무거울 경우 사용한 화물택배사를 입력")
form_layout.addRow("화물택배사:", self.freight_carrier_edit)
self.freight_tracking_edit = QLineEdit()
self.freight_tracking_edit.setPlaceholderText("화물택배 트래킹 번호 입력")
self.freight_tracking_edit.setToolTip("화물택배 전환 시 발행된 트래킹 번호")
form_layout.addRow("화물 트래킹번호:", self.freight_tracking_edit)
self.cs_memo1_edit = QLineEdit()
self.cs_memo1_edit.setPlaceholderText("고객 서비스 메모1 입력")
self.cs_memo1_edit.setToolTip("주문 관련 CS 메모를 입력하세요")
form_layout.addRow("CS 메모1:", self.cs_memo1_edit)
self.cs_memo2_edit = QLineEdit()
self.cs_memo2_edit.setPlaceholderText("고객 서비스 메모2 입력")
self.cs_memo2_edit.setToolTip("추가 CS 메모를 입력하세요")
form_layout.addRow("CS 메모2:", self.cs_memo2_edit)
layout.addLayout(form_layout)
button_layout = QHBoxLayout()
self.complete_button = QPushButton("입력완료")
self.complete_button.setToolTip("주문 정보를 입력하고 저장합니다.")
self.complete_send_button = QPushButton("입력완료 및 1단계 문자발송")
self.complete_send_button.setToolTip("주문 정보를 입력 후 1단계 SMS를 전송합니다.")
self.temp_save_button = QPushButton("임시저장")
self.temp_save_button.setToolTip("주문 정보를 임시 저장합니다.")
self.cancel_button = QPushButton("취소")
self.cancel_button.setToolTip("입력을 취소합니다.")
button_layout.addWidget(self.complete_button)
button_layout.addWidget(self.complete_send_button)
button_layout.addWidget(self.temp_save_button)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
def apply_styles(self):
style = """
QDialog {
background-color: #ffffff;
}
QLineEdit, QComboBox {
padding: 4px;
border: 1px solid #ccc;
border-radius: 4px;
}
QPushButton {
background-color: #1976D2;
color: white;
border-radius: 4px;
padding: 6px 12px;
}
QPushButton:hover {
background-color: #1565C0;
}
"""
self.setStyleSheet(style)

78
gui/settings_dialog.py Normal file
View File

@ -0,0 +1,78 @@
# gui/settings_dialog.py
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QListWidget, QLabel, QMessageBox
from PySide6.QtCore import QSettings
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("사용자 설정")
self.resize(400, 300)
self.settings = QSettings("MyCompany", "MySMSApp")
self.setup_ui()
self.load_shop_names()
def setup_ui(self):
layout = QVBoxLayout(self)
self.shop_list = QListWidget()
self.shop_list.setToolTip("저장된 샵 이름 목록을 확인하세요.")
layout.addWidget(QLabel("저장된 샵 이름:"))
layout.addWidget(self.shop_list)
input_layout = QHBoxLayout()
self.shop_input = QLineEdit()
self.shop_input.setPlaceholderText("새 샵 이름 입력")
self.shop_input.setToolTip("새로운 샵 이름을 입력하세요.")
input_layout.addWidget(self.shop_input)
self.add_button = QPushButton("추가")
self.add_button.setToolTip("새 샵 이름을 추가합니다.")
self.add_button.clicked.connect(self.add_shop_name)
input_layout.addWidget(self.add_button)
layout.addLayout(input_layout)
button_layout = QHBoxLayout()
self.delete_button = QPushButton("삭제")
self.delete_button.setToolTip("선택한 샵 이름을 삭제합니다.")
self.delete_button.clicked.connect(self.delete_shop_name)
self.close_button = QPushButton("닫기")
self.close_button.clicked.connect(self.accept)
button_layout.addWidget(self.delete_button)
button_layout.addWidget(self.close_button)
layout.addLayout(button_layout)
def load_shop_names(self):
shop_names = self.settings.value("shop_names", [])
if not isinstance(shop_names, list):
shop_names = [shop_names] if shop_names else []
self.shop_list.clear()
for name in shop_names:
self.shop_list.addItem(name)
def add_shop_name(self):
name = self.shop_input.text().strip()
if not name:
return
current_names = self.settings.value("shop_names", [])
if not isinstance(current_names, list):
current_names = [current_names] if current_names else []
if name in current_names:
QMessageBox.warning(self, "중복", "이미 존재하는 샵 이름입니다.")
return
current_names.append(name)
self.settings.setValue("shop_names", current_names)
self.load_shop_names()
self.shop_input.clear()
def delete_shop_name(self):
selected_items = self.shop_list.selectedItems()
if not selected_items:
return
current_names = self.settings.value("shop_names", [])
if not isinstance(current_names, list):
current_names = [current_names] if current_names else []
for item in selected_items:
name = item.text()
if name in current_names:
current_names.remove(name)
self.settings.setValue("shop_names", current_names)
self.load_shop_names()

286
gui/template_card.py Normal file
View File

@ -0,0 +1,286 @@
# gui/template_card.py
import re
from PySide6.QtWidgets import QFrame, QVBoxLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QWidget, QHBoxLayout, QMessageBox
from PySide6.QtCore import Signal, Qt
from PySide6.QtGui import QMouseEvent, QKeyEvent
# 주문 단계 목록 (하드코딩; 추후 config 등으로 분리 가능)
ORDER_STEPS = {
1: "주문 접수",
2: "배송대행지 도착",
3: "국내 도착 및 통관 시작",
4: "통관 완료 및 국내 배송 시작",
5: "화물 전환",
6: "통관번호 요청",
7: "통관번호 재요청",
}
class TemplateCard(QFrame):
edit_completed = Signal(int, str, str) # (템플릿 id, 수정된 이름, 수정된 내용)
delete_requested = Signal(int) # (템플릿 id)
clicked = Signal(int)
# 클래스 변수: 현재 수정 모드에 들어간 카드(TemplateCard 객체)를 저장
currently_editing = None
def __init__(self, template_id: int = None, name: str = "", content: str = "",
is_plus: bool = False, parent=None):
"""
:param template_id: 기존 템플릿이면 ID가 있고, 템플릿 추가용(플러스 카드) 경우 None (임시로 -1 사용)
:param is_plus: True이면 플러스 카드( 템플릿 추가용) 처리
"""
super().__init__(parent)
self.template_id = template_id if template_id is not None else -1
self.name = name
self.content = content
self.is_plus = is_plus
self.selected = False
self.edit_mode = False
self.setFixedSize(500, 300)
self.setup_ui()
self.apply_styles()
def setup_ui(self):
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(10, 10, 10, 10)
self.layout.setSpacing(5)
# 상단: 번호 필드 또는 플러스 표시
if not self.is_plus:
self.top_label = QLabel(f"#{self.template_id} - {self.name}")
else:
self.top_label = QLabel("+ 추가")
self.top_label.setFixedHeight(30)
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.layout.addWidget(self.top_label)
# 중간: 내용 필드 (초기에는 보기 전용)
self.content_label = QLabel(self.format_content(self.content))
self.content_label.setWordWrap(True)
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.content_label.setTextFormat(Qt.RichText)
self.layout.addWidget(self.content_label)
# 하단: 선택 여부 및 글자 수
self.bottom_label = QLabel(f"글자 수: {len(self.content)} | 선택: {'선택됨' if self.selected else '미선택'}")
self.bottom_label.setFixedHeight(20)
self.bottom_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.layout.addWidget(self.bottom_label)
def apply_styles(self):
base_style = "QFrame { border: 1px solid #ccc; border-radius: 4px; background-color: #fff; }"
if self.selected:
base_style = "QFrame { border: 2px solid #1976D2; border-radius: 4px; background-color: #fff; }"
self.setStyleSheet(base_style)
self.top_label.setStyleSheet("QLabel { font-size: 16px; font-weight: bold; }")
self.bottom_label.setStyleSheet("QLabel { font-size: 12px; color: #777; }")
def format_content(self, text: str) -> str:
def replacer(match):
var = match.group(0)
return f'<b style="color: #d32f2f;">{var}</b>'
formatted = re.sub(r"\{[^}]+\}", replacer, text)
return f'<div style="font-size:14px;">{formatted}</div>'
def mouseDoubleClickEvent(self, event: QMouseEvent):
# 만약 이미 다른 카드가 수정 모드에 있다면 무시
if TemplateCard.currently_editing is not None and TemplateCard.currently_editing is not self:
QMessageBox.warning(self, "수정 불가", "한 번에 하나의 템플릿만 수정할 수 있습니다.")
return
self.enter_edit_mode()
super().mouseDoubleClickEvent(event)
def mousePressEvent(self, event: QMouseEvent):
# 수정 모드가 아니라면 클릭 이벤트 전달
if not self.edit_mode:
self.clicked.emit(self.template_id)
super().mousePressEvent(event)
def keyPressEvent(self, event: QKeyEvent):
# 수정 모드에서 ESC 키가 눌리면, 수정 취소 처리
if self.edit_mode and event.key() == Qt.Key_Escape:
reply = QMessageBox.question(self, "편집 취소", "수정 중인 내용이 있습니다. 저장하지 않고 취소하시겠습니까?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.cancel_edit_mode()
event.accept()
return
super().keyPressEvent(event)
def set_selected(self, selected: bool):
self.selected = selected
self.update_bottom_label()
self.apply_styles()
def update_bottom_label(self):
self.bottom_label.setText(f"글자 수: {len(self.content)} | 선택: {'선택됨' if self.selected else '미선택'}")
def enter_edit_mode(self):
if self.edit_mode:
return
self.edit_mode = True
TemplateCard.currently_editing = self
# 상단: 기존 QLabel을 QLineEdit으로 교체
self.layout.removeWidget(self.top_label)
self.top_label.deleteLater()
self.name_edit = QLineEdit(self.name)
self.name_edit.setToolTip("템플릿 이름을 수정하세요")
self.name_edit.setFixedHeight(30)
self.layout.insertWidget(0, self.name_edit)
# 중간: 기존 내용 라벨을 편집 위젯과 변수 버튼 패널이 있는 복합 위젯으로 교체
self.layout.removeWidget(self.content_label)
self.content_label.deleteLater()
edit_container = QWidget()
h_layout = QHBoxLayout(edit_container)
h_layout.setContentsMargins(0, 0, 0, 0)
h_layout.setSpacing(5)
self.content_edit = QTextEdit(self.content)
self.content_edit.setToolTip("템플릿 내용을 수정하세요. 오른쪽 버튼을 클릭하면 변수를 삽입할 수 있습니다.")
self.content_edit.setFixedHeight(200)
h_layout.addWidget(self.content_edit, 1)
# 변수 버튼 패널
var_panel = QWidget()
var_layout = QVBoxLayout(var_panel)
var_layout.setContentsMargins(0, 0, 0, 0)
var_layout.setSpacing(5)
variables = [
("{shop_name}", "샵이름"),
("{order_shop}", "주문몰 이름"),
("{taobao_tracking}", "타오트래킹번호"),
("{domestic_tracking}", "국내트래킹번호"),
("{freight_tracking}", "화물택배번호"),
("{product_name}", "상품명")
]
for var, label_text in variables:
btn = QPushButton(label_text)
btn.setToolTip(f"이 버튼을 클릭하면 {var} 변수가 삽입됩니다.")
btn.clicked.connect(lambda checked, v=var: self.insert_variable(v))
btn.setStyleSheet("""
QPushButton {
background-color: #87CEFA;
border: 1px solid #5DADE2;
border-radius: 4px;
padding: 4px;
font-weight: bold;
}
QPushButton:pressed {
background-color: #5DADE2;
}
""")
var_layout.addWidget(btn)
var_panel.setFixedWidth(120)
h_layout.addWidget(var_panel)
self.layout.insertWidget(1, edit_container)
# 하단: 수정 완료 버튼과 삭제 버튼을 같은 QHBoxLayout에 배치
self.button_layout = QHBoxLayout()
self.finish_button = QPushButton("수정 완료")
self.finish_button.setToolTip("수정 내용을 저장하고 보기 모드로 전환합니다.")
self.finish_button.clicked.connect(self.finish_edit_mode)
self.delete_button = QPushButton("삭제")
self.delete_button.setToolTip("템플릿을 삭제합니다.")
self.delete_button.clicked.connect(self.request_delete)
self.update_delete_button_state()
self.name_edit.textChanged.connect(self.update_delete_button_state)
self.content_edit.textChanged.connect(self.update_delete_button_state)
self.button_layout.addWidget(self.finish_button)
self.button_layout.addWidget(self.delete_button)
self.layout.addLayout(self.button_layout)
def update_delete_button_state(self):
if hasattr(self, "name_edit") and hasattr(self, "content_edit"):
if self.name_edit.text().strip() or self.content_edit.toPlainText().strip():
self.delete_button.setEnabled(True)
else:
self.delete_button.setEnabled(False)
def insert_variable(self, var: str):
self.content_edit.insertPlainText(var)
def request_delete(self):
reply = QMessageBox.question(self, "템플릿 삭제", "이 템플릿을 삭제하시겠습니까?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.delete_requested.emit(self.template_id)
self.close()
def finish_edit_mode(self):
self.name = self.name_edit.text().strip()
self.content = self.content_edit.toPlainText().strip()
self.layout.removeWidget(self.name_edit)
self.name_edit.deleteLater()
self.top_label = QLabel(f"#{self.template_id if self.template_id != -1 else ''} - {self.name}")
self.top_label.setFixedHeight(30)
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.layout.insertWidget(0, self.top_label)
# 편집 영역 복합 위젯 삭제 및 원래 내용 라벨 생성
container = self.layout.itemAt(1).widget()
self.layout.removeWidget(container)
container.deleteLater()
self.content_label = QLabel(self.format_content(self.content))
self.content_label.setWordWrap(True)
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.content_label.setTextFormat(Qt.RichText)
self.layout.insertWidget(1, self.content_label)
# 버튼 레이아웃 삭제
while self.button_layout.count():
item = self.button_layout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
self.layout.removeItem(self.button_layout)
self.edit_mode = False
TemplateCard.currently_editing = None
self.update_bottom_label()
self.edit_completed.emit(self.template_id, self.name, self.content)
def cancel_edit_mode(self):
"""
수정 모드를 취소하고, 수정 전의 보기 모드 상태로 복원합니다.
수정 내용은 저장하지 않고 원래 상태로 복원합니다.
"""
# 수정 모드 상태에서 저장되지 않은 변경은 버리고, 기존 값으로 복원하도록 처리합니다.
# 기존 위젯들을 복원하는 코드는 finish_edit_mode와 유사하게 처리하되, 수정 내용을 반영하지 않습니다.
if not self.edit_mode:
return
self.edit_mode = False
TemplateCard.currently_editing = None
# 상단 QLineEdit를 제거하고, 원래의 QLabel을 생성합니다.
if hasattr(self, "name_edit"):
self.layout.removeWidget(self.name_edit)
self.name_edit.deleteLater()
self.top_label = QLabel(f"#{self.template_id if self.template_id != -1 else ''} - {self.name}")
self.top_label.setFixedHeight(30)
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
self.layout.insertWidget(0, self.top_label)
# 중간 편집 영역(복합 위젯)을 제거하고, 원래의 내용 QLabel을 생성합니다.
if hasattr(self, "content_edit") and self.content_edit is not None:
container = self.layout.itemAt(1).widget()
if container is not None:
self.layout.removeWidget(container)
container.deleteLater()
self.content_label = QLabel(self.format_content(self.content))
self.content_label.setWordWrap(True)
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
self.content_label.setTextFormat(Qt.RichText)
self.layout.insertWidget(1, self.content_label)
# 하단 버튼 레이아웃 제거
if hasattr(self, "button_layout"):
while self.button_layout.count():
item = self.button_layout.takeAt(0)
widget = item.widget()
if widget:
widget.deleteLater()
self.layout.removeItem(self.button_layout)
self.update_bottom_label()
# 수정 취소 완료 후, 편집 완료 신호를 보내지 않습니다.

View File

@ -0,0 +1,182 @@
# gui/template_management_dialog.py
from PySide6.QtWidgets import (QDialog, QHBoxLayout, QVBoxLayout, QListWidget, QScrollArea, QWidget, QMessageBox)
from PySide6.QtCore import Qt, QSettings, Slot
from gui.template_card import TemplateCard
from src.database_module import DatabaseManager
from gui.template_card import ORDER_STEPS # 또는 별도로 ORDER_STEPS 상수를 정의
class TemplateManagementDialog(QDialog):
def __init__(self, logger, parent=None):
super().__init__(parent)
self.logger = logger
self.setWindowTitle("템플릿 관리")
self.resize(800, 600)
self.settings = QSettings("MyCompany", "MySMSApp")
self.current_stage = 1
self.selected_template_id = None
self.db_manager = DatabaseManager()
self.plus_card = None # 플러스 카드는 한 번만 생성하도록 함
self.setup_ui()
self.apply_styles()
self.load_order_steps()
self.load_templates_for_stage(self.current_stage)
self.restore_settings()
def setup_ui(self):
main_layout = QHBoxLayout(self)
from PySide6.QtWidgets import QListWidget
self.step_list = QListWidget()
self.step_list.setToolTip("주문 단계별 템플릿을 확인하려면 단계를 선택하세요.")
for stage, name in ORDER_STEPS.items():
self.step_list.addItem(f"{stage}: {name}")
self.step_list.currentRowChanged.connect(self.on_step_changed)
main_layout.addWidget(self.step_list, 1)
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
self.card_container = QWidget()
self.card_layout = QVBoxLayout(self.card_container)
self.card_layout.setSpacing(10)
self.scroll_area.setWidget(self.card_container)
main_layout.addWidget(self.scroll_area, 3)
def apply_styles(self):
style = """
QDialog { background-color: #fafafa; }
QListWidget { border: 1px solid #ccc; border-radius: 4px; font-size: 14px; padding: 4px; }
QListWidget::item:selected { background-color: #1976D2; color: white; border: 1px solid #115293; }
QScrollArea { background-color: #ffffff; }
"""
self.setStyleSheet(style)
def load_order_steps(self):
self.step_list.setCurrentRow(0)
def load_templates_for_stage(self, stage: int):
self.current_stage = stage
# 기존 카드 레이아웃의 모든 위젯 삭제
while self.card_layout.count():
item = self.card_layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
self.template_cards = {}
# DB에서 해당 단계의 템플릿 불러오기
templates = self.db_manager.get_templates_by_stage(stage)
for tpl in templates:
card = TemplateCard(tpl.id, tpl.name, tpl.content, is_plus=False)
card.clicked.connect(self.on_template_selected)
card.edit_completed.connect(self.on_template_edited)
card.delete_requested.connect(self.on_template_deleted)
self.card_layout.addWidget(card)
self.template_cards[tpl.id] = card
# 플러스 카드 추가: plus_card은 단 한 번만 생성
if self.plus_card is None:
self.plus_card = TemplateCard(is_plus=True)
self.plus_card.clicked.connect(self.on_add_template)
self.plus_card.edit_completed.connect(self.on_new_template_added)
self.card_layout.addWidget(self.plus_card)
@Slot(int)
def on_step_changed(self, row: int):
stage = row + 1
# 현재 수정 모드에 있는 카드가 있으면 수정 취소를 시도
if TemplateCard.currently_editing is not None:
editing_card = TemplateCard.currently_editing
try:
editing_card.cancel_edit_mode()
except Exception as e:
self.logger.log(f"수정 모드 취소 중 오류 발생: {e}", level=1)
finally:
TemplateCard.currently_editing = None
self.logger.log(f"주문 단계 변경: {stage} - {ORDER_STEPS.get(stage)}", level=1)
self.load_templates_for_stage(stage)
last_template = self.settings.value(f"selected_template_stage_{stage}", None)
if last_template is not None:
try:
last_template = int(last_template)
self.select_template(last_template)
except ValueError:
pass
@Slot(int)
def on_template_deleted(self, template_id: int):
# 삭제 요청이 들어오면 DB에서 삭제 후 UI 갱신
from PySide6.QtWidgets import QMessageBox
reply = QMessageBox.question(self, "템플릿 삭제", "이 템플릿을 삭제하시겠습니까?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.db_manager.delete_template(template_id)
self.logger.log(f"템플릿 삭제됨: ID {template_id}", level=1)
self.load_templates_for_stage(self.current_stage)
@Slot(int)
def on_template_selected(self, template_id: int):
# 현재 수정 모드에 있는 카드가 있다면
if TemplateCard.currently_editing is not None:
editing_card = TemplateCard.currently_editing
# 이미 편집 중인 카드가 선택하려는 카드와 다르면
if editing_card.template_id != template_id:
reply = QMessageBox.question(
self,
"편집 취소",
"현재 템플릿을 편집 중입니다. 편집을 취소하고 다른 템플릿을 선택하시겠습니까?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
editing_card.cancel_edit_mode() # 수정 취소 처리
else:
return # 선택 전환 취소
# 모든 카드의 선택 해제 시도 (이미 삭제된 위젯은 예외로 제거)
for tpl_id, card in list(self.template_cards.items()):
try:
if not card.edit_mode:
card.set_selected(False)
except RuntimeError:
del self.template_cards[tpl_id]
# 선택하려는 템플릿 카드가 존재하면 선택 처리
if template_id in self.template_cards:
try:
self.template_cards[template_id].set_selected(True)
except RuntimeError:
pass
self.selected_template_id = template_id
self.settings.setValue(f"selected_template_stage_{self.current_stage}", template_id)
self.logger.log(f"템플릿 선택됨: ID {template_id}", level=1)
@Slot()
def on_add_template(self):
# 플러스 카드가 클릭되면, 해당 PlusCard를 수정 모드로 전환하여 새 템플릿 추가를 시작합니다.
sender = self.sender()
if sender:
sender.enter_edit_mode()
@Slot(int, str, str)
def on_new_template_added(self, template_id: int, name: str, content: str):
# 새 템플릿 추가 완료 후 DB에 저장하고 UI 갱신
new_template = self.db_manager.insert_template(stage=self.current_stage, name=name, content=content, is_default=False)
self.logger.log(f"새 템플릿 추가됨: {new_template.name}", level=1)
self.load_templates_for_stage(self.current_stage)
@Slot(int, str, str)
def on_template_edited(self, template_id: int, name: str, content: str):
# 기존 템플릿 수정 완료 시 DB 업데이트
self.db_manager.update_template(template_id, name=name, content=content)
self.logger.log(f"템플릿 수정됨: ID {template_id}", level=1)
self.load_templates_for_stage(self.current_stage)
def select_template(self, template_id: int):
if template_id in self.template_cards:
self.on_template_selected(template_id)
def restore_settings(self):
last_step = self.settings.value("last_selected_step", 1)
self.step_list.setCurrentRow(int(last_step) - 1)
self.logger.log("템플릿 설정 복원 완료", level=1)
def closeEvent(self, event):
# 다이얼로그가 닫힐 때 현재 수정 모드에 있는 카드가 있다면 초기화
TemplateCard.currently_editing = None
super().closeEvent(event)

20
main.py Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
import sys
from PySide6.QtWidgets import QApplication
from gui.main_window import MainWindow
from src.logger_module import Logger
def main():
app = QApplication(sys.argv)
# 로거 초기화: GUI 로그 콜백은 메인창의 로그 창에 append하도록 연결 (MainWindow에서 설정)
logger = Logger(log_file="app.log")
# 메인 창 생성 (여기서 logger를 전달하여 GUI 로그 출력)
window = MainWindow(logger)
window.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()

BIN
orders.db Normal file

Binary file not shown.

124
src/database_module.py Normal file
View File

@ -0,0 +1,124 @@
# database_module.py
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import declarative_base, sessionmaker, relationship
from datetime import datetime, timezone
Base = declarative_base()
class Order(Base):
__tablename__ = "orders"
id = Column(Integer, primary_key=True, autoincrement=True)
customer_name = Column(String)
product_name = Column(String) # 추가: 주문서에 저장된 상품명
shop_name = Column(String) # 샵 이름
order_market = Column(String)
customer_phone = Column(String, nullable=False)
order_step = Column(Integer, nullable=False) # 1~5 주문 단계
taobao_tracking = Column(String)
domestic_tracking = Column(String)
freight_tracking = Column(String)
cs_memo1 = Column(Text)
cs_memo2 = Column(Text)
template_id = Column(Integer, ForeignKey("templates.id"))
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
template = relationship("Template", back_populates="orders")
class Template(Base):
__tablename__ = "templates"
id = Column(Integer, primary_key=True, autoincrement=True)
stage = Column(Integer, nullable=False)
name = Column(String)
content = Column(Text) # 템플릿 내용에 {taobao_tracking}, {domestic_tracking}, {freight_tracking}, {shop_name}, {order_shop}, {product_name} 등 포함 가능
is_default = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
orders = relationship("Order", back_populates="template")
class DatabaseManager:
def __init__(self, db_url="sqlite:///orders.db"):
self.engine = create_engine(db_url, echo=False)
Base.metadata.create_all(self.engine)
self.Session = sessionmaker(bind=self.engine)
# ---- Orders CRUD ----
def insert_order(self, **kwargs):
session = self.Session()
order = Order(**kwargs)
session.add(order)
session.commit()
session.refresh(order)
session.close()
return order
def update_order(self, order_id, **kwargs):
session = self.Session()
order = session.query(Order).filter(Order.id == order_id).first()
if order:
for key, value in kwargs.items():
setattr(order, key, value)
session.commit()
session.close()
return order
def get_order(self, order_id):
session = self.Session()
order = session.query(Order).filter(Order.id == order_id).first()
session.close()
return order
def get_all_orders(self):
session = self.Session()
orders = session.query(Order).all()
session.close()
return orders
def delete_order(self, order_id):
session = self.Session()
order = session.query(Order).filter(Order.id == order_id).first()
if order:
session.delete(order)
session.commit()
session.close()
# ---- Templates CRUD ----
def insert_template(self, **kwargs):
session = self.Session()
template = Template(**kwargs)
session.add(template)
session.commit()
session.refresh(template)
session.close()
return template
def update_template(self, template_id, **kwargs):
session = self.Session()
template = session.query(Template).filter(Template.id == template_id).first()
if template:
for key, value in kwargs.items():
setattr(template, key, value)
session.commit()
session.close()
return template
def get_template(self, template_id):
session = self.Session()
template = session.query(Template).filter(Template.id == template_id).first()
session.close()
return template
def get_templates_by_stage(self, stage):
session = self.Session()
templates = session.query(Template).filter(Template.stage == stage).all()
session.close()
return templates
def delete_template(self, template_id):
session = self.Session()
template = session.query(Template).filter(Template.id == template_id).first()
if template:
session.delete(template)
session.commit()
session.close()

99
src/logger_module.py Normal file
View File

@ -0,0 +1,99 @@
import logging
from logging.handlers import RotatingFileHandler
from PySide6.QtCore import QObject, Signal
import traceback
import inspect
class Logger(QObject):
log_signal = Signal(str) # GUI로 로그 메시지를 전달할 시그널
def __init__(self, gui_logger=None, log_file="app.log", logger_name="MainLogger", level=logging.INFO):
"""
Logger 초기화
:param gui_logger: GUI에 로그를 출력할 콜백 함수
:param log_file: 로그 파일 이름
:param logger_name: 로거 이름
:param level: 기본 로그 레벨
"""
super().__init__()
self.gui_logger = gui_logger
# 로그 설정
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(level) # 로거 레벨 설정
# 포맷 설정
self.simple_format = "[%(asctime)s] [%(levelname)s] %(message)s"
self.detailed_format = (
"[%(asctime)s] [%(threadName)s] [%(levelname)s] "
"[%(filename)s:%(funcName)s:%(lineno)d] %(message)s"
)
# 핸들러 추가
self._add_console_handler(level)
self._add_file_handler(log_file, level)
# GUI Logger 연결
if self.gui_logger:
self.log_signal.connect(self.gui_logger)
def _add_console_handler(self, level):
"""콘솔 핸들러 추가"""
console_handler = logging.StreamHandler()
console_handler.setLevel(level)
formatter = logging.Formatter(
self.detailed_format if level <= logging.DEBUG else self.simple_format
)
console_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
def set_gui_logger(self, gui_logger):
self.gui_logger = gui_logger
def _add_file_handler(self, log_file, level):
"""파일 핸들러 추가"""
file_handler = RotatingFileHandler(
log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
)
file_handler.setLevel(level)
formatter = logging.Formatter(
self.detailed_format if level <= logging.DEBUG else self.simple_format
)
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
def log(self, message, level=logging.INFO, exc_info=False):
"""로그 메시지 기록"""
if exc_info:
message = f"{message}\n{traceback.format_exc()}"
# 호출 위치 정보를 동적으로 추출
caller_frame = logging.currentframe().f_back
record = self.logger.makeRecord(
self.logger.name, level, caller_frame.f_code.co_filename,
caller_frame.f_lineno, message, None, None, caller_frame.f_code.co_name
)
# 로거에 메시지 전달
self.logger.handle(record)
# GUI 로그로 전달 (포맷 적용)
if self.gui_logger:
formatter = logging.Formatter(
self.detailed_format if self.logger.level <= logging.DEBUG else self.simple_format
)
formatted_message = formatter.format(record)
colored_message = self.format_gui_message(formatted_message, level)
self.log_signal.emit(colored_message)
def format_gui_message(self, message, level):
"""GUI 로그 메시지의 HTML 색상 지정"""
color_map = {
logging.DEBUG: "gray",
logging.INFO: "black",
logging.WARNING: "orange",
logging.ERROR: "red",
logging.CRITICAL: "purple",
}
color = color_map.get(level, "black")
return f'<span style="color:{color};">{message}</span>'

65
src/sync_module.py Normal file
View File

@ -0,0 +1,65 @@
# sync_module.py
import asyncio
from supabase import create_client, Client
from database_module import DatabaseManager, Order, Template
from datetime import datetime
def sync_with_supabase(supabase_url: str, supabase_key: str, db_manager: DatabaseManager):
"""
Supabase와 로컬 DB를 동기화하는 함수 (예시).
현재는 서버의 orders와 templates 테이블 전체 데이터를 가져와서 로컬 DB에 반영하는 단순한 예시입니다.
실제 운영에서는 차등 업데이트 로직이 필요합니다.
"""
supabase: Client = create_client(supabase_url, supabase_key)
# Supabase에서 orders 데이터 가져오기
orders_response = supabase.table("orders").select("*").execute()
templates_response = supabase.table("templates").select("*").execute()
# orders_response.data 및 templates_response.data는 리스트 형태임
# 예시: 로컬 DB를 업데이트하기 위해 기존 데이터를 모두 삭제 후 서버 데이터 삽입 (실제 환경에서는 위험하므로 주의)
session = db_manager.Session()
session.query(Order).delete()
session.query(Template).delete()
session.commit()
# orders 동기화
for order in orders_response.data:
# 필요한 경우 문자열을 datetime 객체로 변환하는 로직 추가
new_order = Order(
id=order.get("id"),
customer_name=order.get("customer_name"),
order_market=order.get("order_market"),
customer_phone=order.get("customer_phone"),
order_step=order.get("order_step"),
taobao_tracking=order.get("taobao_tracking"),
domestic_tracking=order.get("domestic_tracking"),
freight_tracking=order.get("freight_tracking"),
cs_memo1=order.get("cs_memo1"),
cs_memo2=order.get("cs_memo2"),
template_id=order.get("template_id"),
created_at=datetime.fromisoformat(order.get("created_at")) if order.get("created_at") else datetime.utcnow(),
updated_at=datetime.fromisoformat(order.get("updated_at")) if order.get("updated_at") else datetime.utcnow(),
)
session.add(new_order)
# templates 동기화
for tpl in templates_response.data:
new_tpl = Template(
id=tpl.get("id"),
stage=tpl.get("stage"),
name=tpl.get("name"),
content=tpl.get("content"),
is_default=tpl.get("is_default") == 1,
created_at=datetime.fromisoformat(tpl.get("created_at")) if tpl.get("created_at") else datetime.utcnow(),
updated_at=datetime.fromisoformat(tpl.get("updated_at")) if tpl.get("updated_at") else datetime.utcnow(),
)
session.add(new_tpl)
session.commit()
session.close()
print("Supabase 동기화 완료.")
# 사용 예시 (동기화는 보통 별도의 스레드나 초기화 시점에 호출):
# db_manager = DatabaseManager("sqlite:///orders.db")
# sync_with_supabase("https://your-supabase-url.supabase.co", "your-supabase-key", db_manager)

197
test.py Normal file
View File

@ -0,0 +1,197 @@
# test_template_insertion.py
from src.database_module import DatabaseManager, Template
from datetime import datetime
def insert_sample_templates():
db_manager = DatabaseManager("sqlite:///orders.db")
sample_templates = [
# [단계 1] 주문 접수
{
"stage": 1,
"name": "주문 접수 기본",
"content": (
"[발송안내]안녕하세요. 혜리수샵입니다.\n"
"롯데온에서 구매하신 접이식 전동차 상품은\n"
"중국 현지에서 발송하여 국제물류센터로 이동중입니다.\n"
"물류센터에서 국내로 발송하게 되면 다시 안내해 드리겠습니다.\n"
"중국현지에서 발송 후 수령까지 약 10일~14일 소요될 예정입니다.\n"
"고객님의 소중한 물품을 안전하게 배송해 드리겠습니다.\n"
"좋은하루 되세요."
),
"is_default": True
},
{
"stage": 1,
"name": "주문 접수 변형",
"content": (
"고객님, 주문이 정상적으로 접수되었습니다.\n"
"(타오바오 트래킹: {taobao_tracking}, 국내 주문번호: {domestic_tracking})"
),
"is_default": False
},
{
"stage": 1,
"name": "주문 접수 상세 안내",
"content": (
"[주문접수안내]안녕하세요, 혜리수샵입니다.\n"
"고객님 주문이 접수되었으며, 곧 중국에서 상품 준비가 시작됩니다.\n"
"타오바오 트래킹 번호 {taobao_tracking} 및 국내 주문번호 {domestic_tracking}이 발행되었습니다.\n"
"추후 배송 진행 상황을 안내해드리겠습니다.\n"
"감사합니다."
),
"is_default": False
},
# [단계 2] 배송대행지 도착
{
"stage": 2,
"name": "배송대행지 도착 기본",
"content": (
"[센터도착안내]안녕하세요. 혜리수샵입니다.\n"
"롯데온에서 구매하신 접이식 전동차상품은\n"
"국제물류센터에 도착하여 금일 국내로 발송예정입니다.\n"
"고객님의 소중한 물품을 안전하게 배송해 드리겠습니다.\n"
"좋은하루 되세요."
),
"is_default": True
},
{
"stage": 2,
"name": "배송대행지 도착 변형",
"content": (
"안녕하세요, 주문하신 상품이 국제물류센터에 도착하였습니다.\n"
"오늘 중으로 국내 배송이 시작될 예정입니다."
),
"is_default": False
},
{
"stage": 2,
"name": "배송대행지 도착 상세 안내",
"content": (
"[배송안내]고객님, 상품이 국제물류센터에 도착하여\n"
"검수 중입니다. 검수가 완료되면 국내로 발송되오니\n"
"배송 상황을 지속적으로 확인해 주세요."
),
"is_default": False
},
# [단계 3] 통관 진행
{
"stage": 3,
"name": "통관 기본",
"content": (
"[통관안내]안녕하세요. 리앤수인터입니다.\n"
"롯데온에서 구매하신 접이식 전동차상품은\n"
"국내에 도착하여 통관 진행 중입니다.\n"
"통관이 완료되는 대로 국내택배가 진행될 예정이며,\n"
"물동량 증가로 인해 통관 지연이 발생할 수 있는 점 양해 부탁드립니다.\n"
"고객님의 소중한 물품을 안전하게 배송해 드리겠습니다.\n"
"좋은하루 되세요."
),
"is_default": True
},
{
"stage": 3,
"name": "통관 변형",
"content": (
"고객님, 상품이 국내에 도착하였습니다.\n"
"현재 통관 절차가 진행 중이며, 통관 완료 시\n"
"국내 배송이 시작됩니다."
),
"is_default": False
},
{
"stage": 3,
"name": "통관 상세 안내",
"content": (
"[통관진행]주문하신 상품이 국내 도착 후\n"
"관세 당국의 통관 심사 중입니다.\n"
"심사 완료 후, 빠른 시일 내에 배송될 예정이오니\n"
"진행 상황은 추후 안내드리겠습니다."
),
"is_default": False
},
# [단계 4] 통관 완료 및 국내 배송 시작
{
"stage": 4,
"name": "통관 완료 기본",
"content": (
"[통관완료안내]안녕하세요 고객님,\n"
"주문하신 접이식 전동차 상품은 통관이 완료되어\n"
"현재 국내배송 준비 중입니다.\n"
"주문번호: {domestic_tracking}을 통해 배송 상태를 확인하실 수 있습니다.\n"
"빠른 시일 내에 고객님께 도착할 예정입니다.\n"
"감사합니다."
),
"is_default": True
},
{
"stage": 4,
"name": "통관 완료 변형",
"content": (
"[배송시작안내]안녕하세요 고객님.\n"
"통관이 완료되어 주문하신 상품의 국내 배송이 시작되었습니다.\n"
"국내배송 주문번호: {domestic_tracking}을 확인해 주세요.\n"
"배송 진행 상황은 실시간으로 안내드리겠습니다."
),
"is_default": False
},
{
"stage": 4,
"name": "국내배송 상세 안내",
"content": (
"[국내배송안내]고객님, 통관이 마무리되었습니다.\n"
"현재 국내택배사에 인계되어 배송이 진행 중입니다.\n"
"주문번호 {domestic_tracking}로 조회 가능하오니 참고 바랍니다.\n"
"최선을 다해 안전하게 배송하겠습니다."
),
"is_default": False
},
# [단계 5] 화물 전환
{
"stage": 5,
"name": "화물 전환 기본",
"content": (
"[화물택배안내]안녕하세요, 혜리수샵입니다.\n"
"고객님 주문 상품이 중량 문제로 인해 일반 택배에서\n"
"화물택배로 전환되었습니다.\n"
"화물택배 트래킹번호: {freight_tracking}를 통해 확인해 주세요.\n"
"배송 일정은 변동될 수 있으니 양해 부탁드립니다."
),
"is_default": True
},
{
"stage": 5,
"name": "화물 전환 변형",
"content": (
"[화물전환안내]고객님, 주문하신 상품이 무게 문제로 인해\n"
"화물택배로 전환되었습니다.\n"
"트래킹번호 {freight_tracking}를 이용하여 배송 상태를 확인하세요.\n"
"빠른 시일 내에 안전하게 배송해 드리겠습니다."
),
"is_default": False
},
{
"stage": 5,
"name": "화물 배송 상세 안내",
"content": (
"[화물배송안내]안녕하세요. 혜리수샵입니다.\n"
"상품이 중량 기준을 초과하여 화물택배로 처리되었습니다.\n"
"화물택배 트래킹번호: {freight_tracking}로 배송 진행 상황을 확인해 주세요.\n"
"불편을 드려 죄송하며, 최선을 다해 배송하겠습니다."
),
"is_default": False
}
]
for tpl in sample_templates:
inserted = db_manager.insert_template(**tpl)
print(f"삽입된 템플릿 - ID: {inserted.id}, 단계: {inserted.stage}, 이름: {inserted.name}")
# db_manager.close()
if __name__ == '__main__':
insert_sample_templates()