initialCommit
This commit is contained in:
commit
1f1fce394e
|
|
@ -0,0 +1,7 @@
|
||||||
|
__pycache__/
|
||||||
|
Lib/
|
||||||
|
Scripts/
|
||||||
|
Include/
|
||||||
|
src/__pycache__
|
||||||
|
*.pyc
|
||||||
|
pyvenv.cfg
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
# 수정 취소 완료 후, 편집 완료 신호를 보내지 않습니다.
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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>'
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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()
|
||||||
Loading…
Reference in New Issue