This commit is contained in:
9700X_PC 2025-02-11 01:49:56 +09:00
parent 4520763b90
commit 1db59535fb
249 changed files with 1241 additions and 277 deletions

BIN
1orders.db Normal file

Binary file not shown.

15
app.log
View File

@ -0,0 +1,15 @@
[2025-02-11 01:38:20,160] [INFO] 시작 중...
[2025-02-11 01:42:17,322] [INFO] 시작 중...
[2025-02-11 01:43:32,874] [INFO] 시작 중...
[2025-02-11 01:45:43,657] [INFO] 시작 중...
[2025-02-11 01:45:44,158] [INFO] 브라우저 연결 완료.
[2025-02-11 01:45:46,397] [INFO] QR 코드가 감지됨. 사용자 로그인 필요.
[2025-02-11 01:47:38,717] [INFO] 시작 중...
[2025-02-11 01:47:39,284] [INFO] 브라우저 연결 완료.
[2025-02-11 01:47:40,574] [INFO] QR 코드가 감지됨. 사용자 로그인 필요.
[2025-02-11 01:47:50,568] [INFO] 이전 로그인 세션이 유지됨. QR 코드 표시 없이 진행.
[2025-02-11 01:49:08,562] [INFO] 시작 중...
[2025-02-11 01:49:09,080] [INFO] 브라우저 연결 완료.
[2025-02-11 01:49:10,197] [INFO] QR 코드가 감지됨. 사용자 로그인 필요.
[2025-02-11 01:49:27,823] [INFO] 수신자 입력란을 찾지 못했습니다: Page.wait_for_selector: Target page, context or browser has been closed
[2025-02-11 01:49:30,406] [INFO] 수신자 입력란을 찾지 못했습니다: Page.wait_for_selector: Target page, context or browser has been closed

View File

@ -1,28 +1,49 @@
# gui/main_window.py # gui/main_window.py
from PySide6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QTextEdit, QMenuBar, QMenu, QLabel, QTableWidget, QTableWidgetItem, QMessageBox)
import asyncio
from datetime import datetime
from PySide6.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QTextEdit, QMenuBar, QMenu, QLabel,
QTableWidget, QTableWidgetItem, QMessageBox
)
from PySide6.QtGui import QAction from PySide6.QtGui import QAction
from PySide6.QtCore import Qt, Slot from PySide6.QtCore import Qt, Slot, QSettings, QTimer
from gui.order_input_dialog import OrderInputDialog from gui.order_input_dialog import OrderInputDialog
from gui.template_management_dialog import TemplateManagementDialog from gui.template_management_dialog import TemplateManagementDialog
from gui.help_dialog import HelpDialog from gui.help_dialog import HelpDialog
from gui.settings_dialog import SettingsDialog from gui.settings_dialog import SettingsDialog
from src.database_module import DatabaseManager from src.database_module import DatabaseManager
from src.sms_module import SMSMessenger # SMS 전송 모듈
# 메인 창 클래스
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self, logger): def __init__(self, logger):
super().__init__() super().__init__()
self.logger = logger self.logger = logger
self.settings = QSettings("When_Ride_Mycar", "SMS_Sender")
self.db_manager = DatabaseManager() # SQLite, SQLAlchemy 기반 DB 관리자 self.db_manager = DatabaseManager() # SQLite, SQLAlchemy 기반 DB 관리자
self.setWindowTitle("주문 알림 SMS 전송 프로그램") self.setWindowTitle("주문 알림 SMS 전송 프로그램")
self.resize(900, 700) self.resize(900, 700)
self.setup_menu() self.setup_menu()
self.setup_ui() self.setup_ui()
self.apply_styles() self.apply_styles()
# SMSMessenger를 생성합니다.
self.sms_messenger = SMSMessenger(self.logger, headless=False, delay=1)
# 이벤트 루프가 실행된 후 (0ms 후) connect()를 실행하도록 예약합니다.
QTimer.singleShot(0, self.start_sms_connection)
# 매달 1일(또는 저장된 월과 현재 월이 다르면) SMS 카운트 초기화
self.check_and_reset_sms_count()
self.update_sms_count_label()
self.refresh_order_list() self.refresh_order_list()
def start_sms_connection(self):
asyncio.create_task(self.sms_messenger.connect())
def setup_menu(self): def setup_menu(self):
menu_bar = self.menuBar() menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("파일") file_menu = menu_bar.addMenu("파일")
@ -57,10 +78,17 @@ class MainWindow(QMainWindow):
main_layout.addLayout(button_layout) main_layout.addLayout(button_layout)
# 주문 목록 테이블 # SMS 발송 건수 표시 (예: "이번달 총 발송건수: X 건")
self.sms_count_label = QLabel()
self.sms_count_label.setToolTip("이번달 총 SMS 발송 건수")
main_layout.addWidget(self.sms_count_label)
# 주문 목록 테이블 (열 수를 8로 설정)
self.order_table = QTableWidget() self.order_table = QTableWidget()
self.order_table.setColumnCount(6) self.order_table.setColumnCount(8)
self.order_table.setHorizontalHeaderLabels(["ID", "상품명", "고객명", "전화번호", "현재단계", "SMS발송여부", "최종 업데이트"]) self.order_table.setHorizontalHeaderLabels([
"ID", "상품명", "고객명", "전화번호", "현재단계", "SMS발송여부", "지금발송", "최종 업데이트"
])
self.order_table.setToolTip("현재 진행 중인 주문 목록을 표시합니다.") self.order_table.setToolTip("현재 진행 중인 주문 목록을 표시합니다.")
main_layout.addWidget(QLabel("주문 목록:")) main_layout.addWidget(QLabel("주문 목록:"))
main_layout.addWidget(self.order_table) main_layout.addWidget(self.order_table)
@ -73,7 +101,6 @@ class MainWindow(QMainWindow):
main_layout.addWidget(self.log_display) main_layout.addWidget(self.log_display)
self.logger.log_signal.connect(self.append_log) self.logger.log_signal.connect(self.append_log)
self.order_table.itemChanged.connect(self.item_changed_slot) self.order_table.itemChanged.connect(self.item_changed_slot)
@Slot(str) @Slot(str)
@ -81,89 +108,140 @@ class MainWindow(QMainWindow):
self.log_display.append(message) self.log_display.append(message)
def open_order_input_dialog(self): def open_order_input_dialog(self):
dialog = OrderInputDialog(self.logger, parent=self) dialog = OrderInputDialog(self.logger, self.db_manager, parent=self)
if dialog.exec(): if dialog.exec():
# 주문 입력 완료 후 주문 목록 새로고침
self.refresh_order_list() self.refresh_order_list()
def open_template_management_dialog(self): def open_template_management_dialog(self):
dialog = TemplateManagementDialog(self.logger, parent=self) dialog = TemplateManagementDialog(self.logger, self.db_manager, parent=self)
dialog.exec() dialog.exec()
def open_settings_dialog(self): def open_settings_dialog(self):
dialog = SettingsDialog(self) dialog = SettingsDialog(self.logger, self.db_manager)
dialog.exec() dialog.exec()
def show_help(self): def show_help(self):
dialog = HelpDialog(parent=self) dialog = HelpDialog(parent=self)
dialog.exec() dialog.exec()
def check_and_reset_sms_count(self):
"""현재 월과 저장된 월을 비교하여, 달이 변경되었으면 SMS 카운트를 초기화"""
current_month = datetime.now().strftime("%Y-%m")
stored_month = self.settings.value("sms_count_month", "")
if stored_month != current_month:
# 새로운 달이 시작되었으므로 카운트를 0으로 초기화하고 저장
self.settings.setValue("sms_count", 0)
self.settings.setValue("sms_count_month", current_month)
def update_sms_count_label(self):
"""SMS 카운트 라벨 업데이트"""
count = self.settings.value("sms_count", 0, type=int)
self.sms_count_label.setText(f"이번달 총 발송건수: {count}")
def increment_sms_count(self):
"""SMS 전송 성공 시 카운트를 1 증가시키고 라벨 업데이트"""
count = self.settings.value("sms_count", 0, type=int)
count += 1
self.settings.setValue("sms_count", count)
self.update_sms_count_label()
def refresh_order_list(self): def refresh_order_list(self):
orders = self.db_manager.get_all_orders() orders = self.db_manager.get_all_orders()
self.order_table.blockSignals(True) # 초기화 시 itemChanged 시그널 방지 self.order_table.blockSignals(True) # 초기화 시 itemChanged 시그널 방지
self.order_table.setRowCount(len(orders)) self.order_table.setRowCount(len(orders))
for row, order in enumerate(orders): for row, order in enumerate(orders):
# 각 셀을 편집 가능하게 설정 # ID (편집 불가)
id_item = QTableWidgetItem(str(order.id)) id_item = QTableWidgetItem(str(order.id))
id_item.setFlags(id_item.flags() & ~Qt.ItemIsEditable) id_item.setFlags(id_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 0, id_item) 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 = QTableWidgetItem(order.product_name or "")
product_item.setFlags(product_item.flags() | Qt.ItemIsEditable) product_item.setFlags(product_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 2, product_item) self.order_table.setItem(row, 1, product_item)
# 고객명 (편집 가능)
name_item = QTableWidgetItem(order.customer_name or "")
name_item.setFlags(name_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 2, name_item)
# 전화번호 (편집 가능)
phone_item = QTableWidgetItem(order.customer_phone) phone_item = QTableWidgetItem(order.customer_phone)
phone_item.setFlags(phone_item.flags() | Qt.ItemIsEditable) phone_item.setFlags(phone_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 3, phone_item) self.order_table.setItem(row, 3, phone_item)
# 현재단계 (편집 가능)
step_item = QTableWidgetItem(str(order.order_step)) step_item = QTableWidgetItem(str(order.order_step))
step_item.setFlags(step_item.flags() & ~Qt.ItemIsEditable) step_item.setFlags(step_item.flags() | Qt.ItemIsEditable)
self.order_table.setItem(row, 4, step_item) self.order_table.setItem(row, 4, step_item)
sms_item = QTableWidgetItem("전송 완료" if order.domestic_tracking else "미전송") # SMS발송여부 (편집 불가)
sms_status = "전송 완료" if order.domestic_tracking else "미전송"
sms_item = QTableWidgetItem(sms_status)
sms_item.setFlags(sms_item.flags() & ~Qt.ItemIsEditable) sms_item.setFlags(sms_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 5, sms_item) self.order_table.setItem(row, 5, sms_item)
# "지금발송" 버튼 (셀 위젯)
send_btn = QPushButton("지금발송")
send_btn.setToolTip("SMS를 즉시 전송합니다.")
# SMS 미전송인 경우에만 버튼 활성화
send_btn.setEnabled(False if order.domestic_tracking else True)
# 버튼 클릭 시 해당 주문의 SMS를 전송하는 비동기 함수 호출
send_btn.clicked.connect(lambda _, o=order: asyncio.create_task(self.send_sms_for_order(o)))
self.order_table.setCellWidget(row, 6, send_btn)
# 최종 업데이트 (편집 불가)
updated_at_str = order.updated_at.strftime("%Y-%m-%d %H:%M:%S") if order.updated_at else "" 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 = QTableWidgetItem(updated_at_str)
update_item.setFlags(update_item.flags() & ~Qt.ItemIsEditable) update_item.setFlags(update_item.flags() & ~Qt.ItemIsEditable)
self.order_table.setItem(row, 6, update_item) self.order_table.setItem(row, 7, update_item)
self.order_table.blockSignals(False) self.order_table.blockSignals(False)
async def send_sms_for_order(self, order):
"""주문 객체를 인자로 받아 SMS를 전송하는 비동기 함수"""
recipient = order.customer_phone
# 실제 템플릿과 주문 데이터를 결합하여 메시지 작성 (여기서는 예시 메시지 사용)
message = "주문 접수 메시지 예시"
self.logger.log(f"Order {order.id}: SMS 전송 시도 (받는 사람: {recipient})", level=1)
result = await self.sms_messenger.send_sms(recipient, message)
self.logger.log(f"Order {order.id}: SMS 전송 결과: {result}", level=1)
if result.get("success"):
# 전송 성공 시 DB 업데이트 및 SMS 발송 카운트 증가
self.db_manager.update_order(order.id, domestic_tracking="전송 완료")
self.increment_sms_count()
self.refresh_order_list()
@Slot() @Slot()
def item_changed_slot(self): def item_changed_slot(self, item):
# 사용자가 셀을 수정한 경우, 수정 내용을 DB에 업데이트하고 확인 메시지를 보여줌 # 사용자가 셀을 수정한 경우, 수정 내용을 DB에 업데이트합니다.
row = self.order_table.currentRow() row = self.order_table.currentRow()
if row < 0: if row < 0:
return return
try:
order_id = int(self.order_table.item(row, 0).text()) order_id = int(self.order_table.item(row, 0).text())
customer_name = self.order_table.item(row, 1).text() except Exception:
product_name = self.order_table.item(row, 2).text() return
customer_name = self.order_table.item(row, 2).text()
product_name = self.order_table.item(row, 1).text()
customer_phone = self.order_table.item(row, 3).text() customer_phone = self.order_table.item(row, 3).text()
# 기타 수정 가능한 필드는 필요에 따라 추가 order_step_text = self.order_table.item(row, 4).text()
try:
# 수정 내용을 데이터베이스에 업데이트 (예시) order_step = int(order_step_text)
updated_order = self.db_manager.update_order(order_id, except ValueError:
QMessageBox.warning(self, "입력 오류", "진행 단계는 정수 값으로 입력해주세요.")
return
updated_order = self.db_manager.update_order(
order_id,
customer_name=customer_name, customer_name=customer_name,
product_name=product_name, product_name=product_name,
customer_phone=customer_phone) customer_phone=customer_phone,
# 확인 메시지 표시 order_step=order_step
)
QMessageBox.information(self, "저장 확인", f"주문서(ID {order_id})가 수정되었습니다.") QMessageBox.information(self, "저장 확인", f"주문서(ID {order_id})가 수정되었습니다.")
self.logger.log(f"주문서(ID {order_id}) 수정: 고객명={customer_name}, 상품명={product_name}, 전화번호={customer_phone}", level=1) self.logger.log(
f"주문서(ID {order_id}) 수정: 고객명={customer_name}, 상품명={product_name}, 전화번호={customer_phone}, 진행단계={order_step}",
def open_settings_dialog(self): level=1
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): def apply_styles(self):
style = """ style = """

View File

@ -1,145 +1,268 @@
# gui/order_input_dialog.py (일부 발췌) # gui/order_input_dialog.py
from PySide6.QtWidgets import QDialog, QFormLayout, QLineEdit, QComboBox, QPushButton, QHBoxLayout, QVBoxLayout, QLabel
from PySide6.QtCore import QSettings import re
import sys
import asyncio
from PySide6.QtWidgets import (
QDialog, QFormLayout, QLineEdit, QComboBox, QPushButton, QHBoxLayout,
QVBoxLayout, QLabel, QMessageBox
)
from src.sms_module import SMSMessenger # SMS 전송 모듈 (테스트 코드 참조)
class OrderInputDialog(QDialog): class OrderInputDialog(QDialog):
def __init__(self, logger, parent=None): def __init__(self, logger, db_manager, parent=None):
super().__init__(parent) super().__init__(parent)
self.logger = logger self.logger = logger
self.db_manager = db_manager
self.setWindowTitle("주문정보 입력") self.setWindowTitle("주문정보 입력")
self.resize(600, 500)
self.sms_messenger = SMSMessenger(headless=False, delay=1) # SMS 테스트 모듈 (필요에 따라 설정)
self.setup_ui() self.setup_ui()
self.apply_styles() 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): def setup_ui(self):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
form_layout = QFormLayout() form_layout = QFormLayout()
# 샵 이름 선택 ComboBox 추가 # 1. 샵 이름 선택 ComboBox: DB에서 shop_names를 가져옴
self.shop_combo = QComboBox() self.shop_combo = QComboBox()
self.shop_combo.setToolTip("주문서에 사용할 샵 이름을 선택하세요") self.shop_combo.setToolTip("주문서에 사용할 샵 이름을 선택하세요")
settings = QSettings("MyCompany", "MySMSApp") shop_names = self.db_manager.get_settings_by_category("shop_names")
shop_names = settings.value("shop_names", []) formatted_shop_names = [f"[{shop.tag}사업자]-{shop.value}" for shop in shop_names]
if not isinstance(shop_names, list): self.shop_combo.addItems(formatted_shop_names)
shop_names = [shop_names] if shop_names else []
self.shop_combo.addItems(shop_names)
form_layout.addRow("샵 이름:", self.shop_combo) form_layout.addRow("샵 이름:", self.shop_combo)
# 2. 고객 이름
self.customer_name_edit = QLineEdit() self.customer_name_edit = QLineEdit()
self.customer_name_edit.setPlaceholderText("고객 이름 입력") self.customer_name_edit.setPlaceholderText("고객 이름 입력")
self.customer_name_edit.setToolTip("고객의 이름을 입력하세요") self.customer_name_edit.setToolTip("고객의 이름을 입력하세요")
form_layout.addRow("고객 이름:", self.customer_name_edit) form_layout.addRow("고객 이름:", self.customer_name_edit)
# 3. 상품명
self.product_name_edit = QLineEdit() self.product_name_edit = QLineEdit()
self.product_name_edit.setPlaceholderText("상품명") self.product_name_edit.setPlaceholderText("상품명 입력")
self.product_name_edit.setToolTip("상품명을 입력하세요") self.product_name_edit.setToolTip("주문하실 상품명을 입력하세요")
form_layout.addRow("상품명:", self.product_name_edit) form_layout.addRow("상품명:", self.product_name_edit)
self.order_market_edit = QLineEdit() # 4. 주문 마켓 선택 ComboBox: DB에서 order_markets를 가져옴
self.order_market_edit.setPlaceholderText("주문 마켓 입력 (예: 쿠팡, 11번가 등)") self.order_market_combo = QComboBox()
self.order_market_edit.setToolTip("주문이 발생한 국내 마켓을 입력하세요") self.order_market_combo.setToolTip("주문이 발생한 국내 마켓을 선택하세요")
form_layout.addRow("주문 마켓:", self.order_market_edit) order_markets = self.db_manager.get_settings_by_category("order_markets")
self.order_market_combo.addItems(order_markets)
form_layout.addRow("주문 마켓:", self.order_market_combo)
# 5. 고객 전화번호 (자동 하이픈 삽입)
self.customer_phone_edit = QLineEdit() self.customer_phone_edit = QLineEdit()
self.customer_phone_edit.setPlaceholderText("010-1234-5678") self.customer_phone_edit.setPlaceholderText("010-1234-5678")
self.customer_phone_edit.setToolTip("고객 전화번호를 입력하세요") self.customer_phone_edit.setToolTip("고객 전화번호를 입력하세요. 입력 중 자동 하이픈이 삽입됩니다.")
self.customer_phone_edit.textChanged.connect(self.format_phone_number_slot)
form_layout.addRow("고객 전화번호:", self.customer_phone_edit) form_layout.addRow("고객 전화번호:", self.customer_phone_edit)
# 6. 타오바오 트래킹 번호
self.taobao_tracking_edit = QLineEdit() self.taobao_tracking_edit = QLineEdit()
self.taobao_tracking_edit.setPlaceholderText("타오바오 트래킹 번호 입력") self.taobao_tracking_edit.setPlaceholderText("타오바오 트래킹 번호 입력")
self.taobao_tracking_edit.setToolTip("타오바오에서 발행된 트래킹 번호") self.taobao_tracking_edit.setToolTip("타오바오에서 발행된 트래킹 번호")
form_layout.addRow("타오바오 트래킹:", self.taobao_tracking_edit) form_layout.addRow("타오바오 트래킹:", self.taobao_tracking_edit)
# 7. 배대지 이름
self.delivery_agent_edit = QLineEdit() self.delivery_agent_edit = QLineEdit()
self.delivery_agent_edit.setPlaceholderText("배대지 이름 입력") self.delivery_agent_edit.setPlaceholderText("배대지 이름 입력")
self.delivery_agent_edit.setToolTip("배송대행지(배대지)의 이름을 입력하세요") self.delivery_agent_edit.setToolTip("배송대행지(배대지)의 이름을 입력하세요")
form_layout.addRow("배대지 이름:", self.delivery_agent_edit) form_layout.addRow("배대지 이름:", self.delivery_agent_edit)
self.pre_carrier_edit = QLineEdit() # 8. 국내 택배사 선택 ComboBox: DB에서 domestic_couriers를 가져옴
self.pre_carrier_edit.setPlaceholderText("선송장 택배사 입력") # (기존 '선송장 택배사' 항목을 국내 택배사로 변경)
self.pre_carrier_edit.setToolTip("주문 발송 시 사용한 택배사를 입력하세요") self.domestic_courier_combo = QComboBox()
form_layout.addRow("선송장 택배사:", self.pre_carrier_edit) self.domestic_courier_combo.setToolTip("주문 발송 시 사용한 국내 택배사를 선택하세요")
domestic_couriers = self.db_manager.get_settings_by_category("domestic_couriers")
self.domestic_courier_combo.addItems(domestic_couriers)
form_layout.addRow("국내 택배사:", self.domestic_courier_combo)
# 9. 국내 트래킹번호
self.tracking_edit = QLineEdit() self.tracking_edit = QLineEdit()
self.tracking_edit.setPlaceholderText("배송대행지 주문 트래킹 번호 입력") self.tracking_edit.setPlaceholderText("배송대행지 주문 트래킹 번호 입력")
self.tracking_edit.setToolTip("배송대행지에서 발행한 국내 트래킹 번호") self.tracking_edit.setToolTip("배송대행지에서 발행한 국내 트래킹 번호")
form_layout.addRow("국내 트래킹번호:", self.tracking_edit) form_layout.addRow("국내 트래킹번호:", self.tracking_edit)
self.freight_carrier_edit = QLineEdit() # 10. 화물택배사 선택 ComboBox: DB에서 cargo_couriers를 가져옴
self.freight_carrier_edit.setPlaceholderText("화물택배사 입력") self.freight_carrier_combo = QComboBox()
self.freight_carrier_edit.setToolTip("물품이 무거울 경우 사용한 화물택배사를 입력") self.freight_carrier_combo.setToolTip("물품이 무거울 경우 사용한 화물택배사를 선택하세요")
form_layout.addRow("화물택배사:", self.freight_carrier_edit) cargo_couriers = self.db_manager.get_settings_by_category("cargo_couriers")
self.freight_carrier_combo.addItems(cargo_couriers)
form_layout.addRow("화물택배사:", self.freight_carrier_combo)
# 11. 화물 트래킹번호
self.freight_tracking_edit = QLineEdit() self.freight_tracking_edit = QLineEdit()
self.freight_tracking_edit.setPlaceholderText("화물택배 트래킹 번호 입력") self.freight_tracking_edit.setPlaceholderText("화물택배 트래킹 번호 입력")
self.freight_tracking_edit.setToolTip("화물택배 전환 시 발행된 트래킹 번호") self.freight_tracking_edit.setToolTip("화물택배 전환 시 발행된 트래킹 번호")
form_layout.addRow("화물 트래킹번호:", self.freight_tracking_edit) form_layout.addRow("화물 트래킹번호:", self.freight_tracking_edit)
# 12. CS 메모1
self.cs_memo1_edit = QLineEdit() self.cs_memo1_edit = QLineEdit()
self.cs_memo1_edit.setPlaceholderText("고객 서비스 메모1 입력") self.cs_memo1_edit.setPlaceholderText("고객 서비스 메모1 입력")
self.cs_memo1_edit.setToolTip("주문 관련 CS 메모를 입력하세요") self.cs_memo1_edit.setToolTip("주문 관련 CS 메모를 입력하세요")
form_layout.addRow("CS 메모1:", self.cs_memo1_edit) form_layout.addRow("CS 메모1:", self.cs_memo1_edit)
# 13. CS 메모2
self.cs_memo2_edit = QLineEdit() self.cs_memo2_edit = QLineEdit()
self.cs_memo2_edit.setPlaceholderText("고객 서비스 메모2 입력") self.cs_memo2_edit.setPlaceholderText("고객 서비스 메모2 입력")
self.cs_memo2_edit.setToolTip("추가 CS 메모를 입력하세요") self.cs_memo2_edit.setToolTip("추가 CS 메모를 입력하세요")
form_layout.addRow("CS 메모2:", self.cs_memo2_edit) form_layout.addRow("CS 메모2:", self.cs_memo2_edit)
self.order_step_combo = QComboBox()
self.order_step_combo.setToolTip("현재 주문의 진행단계")
# order_steps = self.db_manager.get_settings_by_category("cargo_couriers")
order_steps = ["1단계-통관부호 요청", "2단계-통관부호 재요청", "3단계-주문접수", "4단계-배대지도착", "5단계-통관시작", "6단계-국내배송 시작", "7단계-화물택배전환"]
self.order_step_combo.addItems(order_steps)
form_layout.addRow("진행단계:", self.order_step_combo)
layout.addLayout(form_layout) layout.addLayout(form_layout)
# 버튼 영역
button_layout = QHBoxLayout() button_layout = QHBoxLayout()
self.complete_button = QPushButton("입력완료") self.complete_button = QPushButton("입력완료")
self.complete_button.setToolTip("주문 정보를 입력하고 저장합니다.") self.complete_button.setToolTip("주문 정보를 입력하고 저장합니다.")
self.complete_send_button = QPushButton("입력완료 및 1단계 문자발송") self.complete_send_button = QPushButton("입력완료 및 1단계 문자발송")
self.complete_send_button.setToolTip("주문 정보를 입력 후 1단계 SMS를 전송합니다.") self.complete_send_button.setToolTip("주문 정보를 저장 후 1단계 SMS를 전송합니다.")
self.temp_save_button = QPushButton("임시저장") self.temp_save_button = QPushButton("임시저장")
self.temp_save_button.setToolTip("주문 정보를 임시 저장합니다.") self.temp_save_button.setToolTip("주문 정보를 임시 저장합니다.")
self.cancel_button = QPushButton("취소") self.cancel_button = QPushButton("취소")
self.cancel_button.setToolTip("입력을 취소합니다.") self.cancel_button.setToolTip("입력을 취소합니다.")
self.complete_button.clicked.connect(self.on_complete)
self.complete_send_button.clicked.connect(self.on_complete_and_send)
self.temp_save_button.clicked.connect(self.on_temp_save)
self.cancel_button.clicked.connect(self.on_cancel)
button_layout.addWidget(self.complete_button) button_layout.addWidget(self.complete_button)
button_layout.addWidget(self.complete_send_button) button_layout.addWidget(self.complete_send_button)
button_layout.addWidget(self.temp_save_button) button_layout.addWidget(self.temp_save_button)
button_layout.addWidget(self.cancel_button) button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout) layout.addLayout(button_layout)
def apply_styles(self): def apply_styles(self):
style = """ style = """
QDialog { QDialog { background-color: #ffffff; }
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; }
QLineEdit, QComboBox { QPushButton:hover { background-color: #1565C0; }
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) self.setStyleSheet(style)
def format_phone_number(self, text):
# 간단한 규칙 적용: 010으로 시작하면 010-0000-0000, 05로 시작하면 0504-0000-0000 형태로 포맷팅
digits = re.sub(r'\D', '', text)
if digits.startswith("010") and len(digits) >= 10:
return f"{digits[:3]}-{digits[3:7]}-{digits[7:11]}"
elif digits.startswith("05") and len(digits) >= 11:
return f"{digits[:4]}-{digits[4:8]}-{digits[8:12]}"
else:
return text
def format_phone_number_slot(self, text):
formatted = self.format_phone_number(text)
if formatted != text:
self.customer_phone_edit.blockSignals(True)
self.customer_phone_edit.setText(formatted)
self.customer_phone_edit.blockSignals(False)
def validate_phone_number_input(self):
"""
전화번호 입력란의 값을 검증합니다.
- 010으로 시작하면 전체 숫자 10자리여야 합니다. (, "010" + 7자리)
- 05 시작하면 전체 숫자 10자리여야 합니다. (, "05" + 8자리)
"""
text = self.customer_phone_edit.text().strip()
digits = re.sub(r'\D', '', text)
if digits.startswith("010"):
expected_length = 11 # 010(3자리) + 7자리 = 10자리
if len(digits) != expected_length:
QMessageBox.warning(self, "입력 오류", "010으로 시작하는 전화번호는 7자리 숫자(총 10자리)를 입력해야 합니다.")
return False
elif digits.startswith("05"):
expected_length = 12 # 05(2자리) + 8자리 = 10자리
if len(digits) != expected_length:
QMessageBox.warning(self, "입력 오류", "05로 시작하는 전화번호는 8자리 숫자(총 10자리)를 입력해야 합니다.")
return False
else:
QMessageBox.warning(self, "입력 오류", "전화번호는 010 또는 05로 시작해야 합니다.")
return False
return True
def get_order_data(self):
# 주문 진행 단계는 order_step_combo에서 선택한 문자열에서 앞쪽 숫자를 추출합니다.
order_step_text = self.order_step_combo.currentText()
try:
# 예: "3단계-주문접수" → 3
order_step = int(order_step_text.split("단계")[0])
except Exception:
order_step = 1 # 변환 실패 시 기본값 1 사용
data = {
"shop_name": self.shop_combo.currentText(),
"customer_name": self.customer_name_edit.text().strip(),
"product_name": self.product_name_edit.text().strip(),
"order_market": self.order_market_combo.currentText(),
"customer_phone": self.customer_phone_edit.text().strip(),
"taobao_tracking": self.taobao_tracking_edit.text().strip(),
"delivery_agent": self.delivery_agent_edit.text().strip(),
"domestic_courier": self.domestic_courier_combo.currentText(), # 국내 택배사
"domestic_tracking": self.tracking_edit.text().strip(),
"freight_carrier": self.freight_carrier_combo.currentText(),
"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": order_step
}
return data
def on_complete(self):
if not self.validate_phone_number_input():
return
data = self.get_order_data()
if not data["customer_phone"]:
QMessageBox.warning(self, "입력 오류", "고객 전화번호를 입력하세요.")
return
# DB 저장: 주문서를 DatabaseManager.insert_order()로 저장
order = self.db_manager.insert_order(**data)
self.order_data = data
self.logger.log(f"주문 정보 저장됨: {data}", level=1)
QMessageBox.information(self, "저장 확인", f"주문 정보(ID {order.id})가 저장되었습니다.")
self.accept()
def on_complete_and_send(self):
if not self.validate_phone_number_input():
return
# 입력완료와 SMS 전송을 동시에 진행: DB에 저장한 후 SMS 모듈 호출
self.on_complete() # 저장 처리
async def send_sms():
result = await self.sms_messenger.send_sms(
recipient=self.customer_phone_edit.text().strip(),
message="주문 접수 메시지 예시" # 실제 템플릿과 주문 데이터를 결합하여 사용
)
self.logger.log(f"SMS 전송 결과: {result}", level=1)
asyncio.create_task(send_sms())
self.accept()
def on_temp_save(self):
data = self.get_order_data()
self.order_data = data
self.logger.log(f"주문 정보 임시 저장됨: {data}", level=1)
QMessageBox.information(self, "임시 저장", "주문 정보가 임시 저장되었습니다.")
self.accept()
def on_cancel(self):
reply = QMessageBox.question(self, "취소 확인",
"입력 중인 주문 정보를 저장하지 않고 취소하시겠습니까?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
self.reject()
def showEvent(self, event):
# 전화번호 입력 시 자동 포맷팅을 위한 슬롯 연결
self.customer_phone_edit.textChanged.connect(self.format_phone_number_slot)
super().showEvent(event)

View File

@ -1,78 +1,283 @@
# gui/settings_dialog.py # gui/settings_dialog.py
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QListWidget, QLabel, QMessageBox from PySide6.QtWidgets import (
from PySide6.QtCore import QSettings QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
QListWidget, QLabel, QMessageBox, QTabWidget, QWidget, QComboBox
)
from PySide6.QtCore import Qt
class SettingsDialog(QDialog): class ShopTagDialog(QDialog):
def __init__(self, parent=None): """
이름 추가 , 사용 가능한 태그(숫자) 하나를 선택하는 다이얼로그.
available_tags에는 사용되지 않은 태그 숫자 리스트가 전달됩니다.
"""
def __init__(self, available_tags, parent=None):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("사용자 설정") self.setWindowTitle("샵 태그 선택")
self.resize(400, 300) self.selected_tag = None
self.settings = QSettings("MyCompany", "MySMSApp")
self.setup_ui()
self.load_shop_names()
def setup_ui(self):
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
label = QLabel("이 샵의 태그 번호를 선택하세요:")
layout.addWidget(label)
self.shop_list = QListWidget() self.combo = QComboBox()
self.shop_list.setToolTip("저장된 샵 이름 목록을 확인하세요.") # available_tags가 예: [3, 4, 5, 6, 7]라면 콤보박스에는 "3사", "4사", ... 로 표시
layout.addWidget(QLabel("저장된 샵 이름:")) for tag in available_tags:
layout.addWidget(self.shop_list) self.combo.addItem(f"{tag}", tag)
layout.addWidget(self.combo)
input_layout = QHBoxLayout() # 엔터키를 누르면 다이얼로그를 수락하도록 연결합니다.
self.shop_input = QLineEdit() self.combo.lineEdit().returnPressed.connect(self.accept)
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() button_layout = QHBoxLayout()
self.delete_button = QPushButton("삭제") ok_button = QPushButton("확인")
self.delete_button.setToolTip("선택한 샵 이름을 삭제합니다.") cancel_button = QPushButton("취소")
self.delete_button.clicked.connect(self.delete_shop_name) ok_button.clicked.connect(self.accept)
self.close_button = QPushButton("닫기") cancel_button.clicked.connect(self.reject)
self.close_button.clicked.connect(self.accept) button_layout.addWidget(ok_button)
button_layout.addWidget(self.delete_button) button_layout.addWidget(cancel_button)
button_layout.addWidget(self.close_button)
layout.addLayout(button_layout) layout.addLayout(button_layout)
def load_shop_names(self): def exec_(self):
shop_names = self.settings.value("shop_names", []) result = super().exec_()
if not isinstance(shop_names, list): if result == QDialog.Accepted:
shop_names = [shop_names] if shop_names else [] self.selected_tag = self.combo.currentData()
self.shop_list.clear() return result
for name in shop_names:
self.shop_list.addItem(name)
def add_shop_name(self): class SettingsDialog(QDialog):
name = self.shop_input.text().strip() def __init__(self, logger, db_manager, parent=None):
if not name: """
:param logger: 로깅 객체
:param db_manager: DatabaseManager 인스턴스 (database_module.py의 DatabaseManager)
"""
super().__init__(parent)
self.setWindowTitle("사용자 설정")
self.resize(500, 500)
self.logger = logger
self.db_manager = db_manager
# 관리할 항목: key는 DB상의 카테고리명, value는 표시될 이름
self.categories = {
"shop_names": "샵 이름",
"order_markets": "주문마켓",
"domestic_couriers": "국내 택배사",
"cargo_couriers": "화물 택배사"
}
# 탭 순서를 보존하기 위해 key 목록 저장
self.tab_keys = list(self.categories.keys())
self.widgets = {}
self.setup_ui()
self.load_all_settings()
self.setStyleSheet(self.load_style_sheet())
# 탭 변경 시 해당 탭의 입력창에 포커스하도록 연결
self.tab_widget.currentChanged.connect(self.on_tab_changed)
def setup_ui(self):
main_layout = QVBoxLayout(self)
self.tab_widget = QTabWidget()
for key, display_name in self.categories.items():
tab = QWidget()
tab_layout = QVBoxLayout(tab)
# 저장된 항목을 표시하는 리스트 위젯
list_widget = QListWidget()
list_widget.setToolTip(f"저장된 {display_name} 목록을 확인하세요.")
tab_layout.addWidget(list_widget)
# 입력창과 추가 버튼을 담는 수평 레이아웃
input_layout = QHBoxLayout()
line_edit = QLineEdit()
line_edit.setPlaceholderText(f"{display_name} 입력")
line_edit.returnPressed.connect(lambda cat=key: self.add_item(cat))
line_edit.setToolTip(f"새로운 {display_name}을(를) 입력하세요.")
input_layout.addWidget(line_edit)
add_button = QPushButton("추가")
add_button.setToolTip(f"{display_name}을(를) 추가합니다.")
add_button.clicked.connect(lambda _, cat=key: self.add_item(cat))
input_layout.addWidget(add_button)
tab_layout.addLayout(input_layout)
# 엔터키로도 추가 (샵 이름의 경우)
if key == "shop_names":
line_edit.returnPressed.connect(lambda cat=key: self.add_item(cat))
# 삭제 버튼
delete_button = QPushButton("삭제")
delete_button.setToolTip(f"선택한 {display_name}을(를) 삭제합니다.")
delete_button.clicked.connect(lambda _, cat=key: self.delete_item(cat))
tab_layout.addWidget(delete_button, alignment=Qt.AlignRight)
self.tab_widget.addTab(tab, display_name)
index = self.tab_widget.indexOf(tab)
self.tab_widget.setTabToolTip(index, f"{display_name} 설정을 관리합니다.")
self.widgets[key] = {
"list": list_widget,
"input": line_edit,
"add": add_button,
"delete": delete_button
}
main_layout.addWidget(self.tab_widget)
# 닫기 버튼
button_layout = QHBoxLayout()
close_button = QPushButton("닫기")
close_button.setToolTip("설정 창을 닫습니다.")
close_button.clicked.connect(self.accept)
button_layout.addWidget(close_button)
main_layout.addLayout(button_layout)
def on_tab_changed(self, index):
key = self.tab_keys[index]
if key in self.widgets and "input" in self.widgets[key]:
self.widgets[key]["input"].setFocus()
def load_all_settings(self):
"""
카테고리별로 DB에 저장된 값을 읽어와 리스트 위젯에 표시합니다.
이름의 경우, DB에 별도 저장된 tag와 value를 "[tag사업자] shop_name" 형식으로 표시합니다.
"""
for category in self.categories.keys():
self.widgets[category]["list"].clear()
if category == "shop_names":
records = self.db_manager.get_settings_by_category(category)
# records는 각 레코드가 tag와 value 속성을 가지고 있다고 가정합니다.
for rec in records:
display_text = f"[{rec.tag}사업자] {rec.value}"
self.widgets[category]["list"].addItem(display_text)
else:
values = self.db_manager.get_settings_by_category(category)
for item in values:
self.widgets[category]["list"].addItem(item)
def get_available_shop_tags(self, used):
"""
이미 사용 중인 태그 집합을 받아, 사용되지 않은 숫자 _최소 5개_를 반환합니다.
예를 들어 used가 {1, 2}라면 [3, 4, 5, 6, 7] 반환합니다.
:param used: set(int) 이미 사용된 태그 번호
:return: list(int) 사용 가능한 태그 번호 (최소 5)
"""
available = []
num = 1
while len(available) < 5:
if num not in used:
available.append(num)
num += 1
return available
def add_item(self, category):
"""
지정된 카테고리에 항목을 추가합니다.
이름의 경우 태그 선택 다이얼로그를 통해 tag를 선택하고, DB에는 별도의 tag 필드와 함께 저장합니다.
"""
display_name = self.categories.get(category, category)
text = self.widgets[category]["input"].text().strip()
if not text:
return return
current_names = self.settings.value("shop_names", [])
if not isinstance(current_names, list): if category == "shop_names":
current_names = [current_names] if current_names else [] # 이미 등록된 샵 이름(및 태그)을 조회 (DB에서 tag와 value로 관리)
if name in current_names: existing = self.db_manager.get_settings_by_category("shop_names")
used_tags = set()
for rec in existing:
if rec.tag is not None:
used_tags.add(rec.tag)
if rec.value.strip().lower() == text.lower():
QMessageBox.warning(self, "중복", "이미 존재하는 샵 이름입니다.") QMessageBox.warning(self, "중복", "이미 존재하는 샵 이름입니다.")
return return
current_names.append(name) available = self.get_available_shop_tags(used_tags)
self.settings.setValue("shop_names", current_names) tag_dialog = ShopTagDialog(available, self)
self.load_shop_names() if tag_dialog.exec_() == QDialog.Accepted:
self.shop_input.clear() chosen_tag = tag_dialog.selected_tag
else:
return # 취소 시 중단
# DB에 추가: add_shop_setting(category, value, tag)
success = self.db_manager.add_shop_setting("shop_names", text, chosen_tag)
if not success:
QMessageBox.warning(self, "중복", f"이미 존재하는 {display_name}입니다.")
return
else:
final_value = text
success = self.db_manager.add_setting(category, final_value)
if not success:
QMessageBox.warning(self, "중복", f"이미 존재하는 {display_name}입니다.")
return
def delete_shop_name(self): self.load_all_settings()
selected_items = self.shop_list.selectedItems() self.widgets[category]["input"].clear()
def delete_item(self, category):
"""
지정된 카테고리에서 선택한 항목을 삭제합니다.
이름의 경우 "[tag사] shop_name"에서 tag와 shop_name을 추출하여 삭제합니다.
"""
selected_items = self.widgets[category]["list"].selectedItems()
if not selected_items: if not selected_items:
return 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: for item in selected_items:
name = item.text() if category == "shop_names":
if name in current_names: text = item.text()
current_names.remove(name) if text.startswith("[") and "]" in text:
self.settings.setValue("shop_names", current_names) try:
self.load_shop_names() tag_str = text.split("]")[0][1:] # 예: "[3사" -> "3사"
# tag_str이 "3사"이면 tag를 정수로 변환
tag_num = int(tag_str.replace("", ""))
shop_name = text.split("]", 1)[1].strip()
self.db_manager.delete_shop_setting(category, shop_name, tag_num)
except Exception:
pass
else:
self.db_manager.delete_setting(category, item.text())
self.load_all_settings()
def load_style_sheet(self):
"""
현대적이고 모던한 느낌의 스타일 시트를 반환합니다.
"""
return """
QDialog {
background-color: #f7f9fc;
font-family: 'Segoe UI', sans-serif;
font-size: 10pt;
}
QTabWidget::pane {
border: 1px solid #dcdcdc;
background: #ffffff;
}
QTabBar::tab {
background: #e8eff7;
border: 1px solid #dcdcdc;
padding: 8px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
margin-right: 2px;
min-width: 100px;
}
QTabBar::tab:selected {
background: #ffffff;
border-bottom: 2px solid #3498db;
font-weight: bold;
}
QLineEdit {
border: 1px solid #dcdcdc;
padding: 6px;
border-radius: 4px;
background-color: #ffffff;
}
QPushButton {
background-color: #3498db;
color: #ffffff;
border: none;
padding: 6px 12px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #2980b9;
}
QListWidget {
background: #ffffff;
border: 1px solid #dcdcdc;
padding: 4px;
border-radius: 4px;
}
"""

View File

@ -7,13 +7,13 @@ from PySide6.QtGui import QMouseEvent, QKeyEvent
# 주문 단계 목록 (하드코딩; 추후 config 등으로 분리 가능) # 주문 단계 목록 (하드코딩; 추후 config 등으로 분리 가능)
ORDER_STEPS = { ORDER_STEPS = {
1: "주문 접수", 1: "통관번호 요청",
2: "배송대행지 도착", 2: "통관번호 재요청",
3: "국내 도착 및 통관 시작", 3: "주문 접수",
4: "통관 완료 및 국내 배송 시작", 4: "배송대행지 도착",
5: "화물 전환", 5: "국내 도착 및 통관 시작",
6: "통관번호 요청", 6: "통관 완료 및 국내 배송 시작",
7: "통관번호 재요청", 7: "화물 전환",
} }
@ -48,7 +48,7 @@ class TemplateCard(QFrame):
self.layout.setSpacing(5) self.layout.setSpacing(5)
# 상단: 번호 필드 또는 플러스 표시 # 상단: 번호 필드 또는 플러스 표시
if not self.is_plus: if not self.is_plus:
self.top_label = QLabel(f"#{self.template_id} - {self.name}") self.top_label = QLabel(f"# {self.name}")
else: else:
self.top_label = QLabel("+ 추가") self.top_label = QLabel("+ 추가")
self.top_label.setFixedHeight(30) self.top_label.setFixedHeight(30)

View File

@ -1,20 +1,18 @@
# gui/template_management_dialog.py # gui/template_management_dialog.py
from PySide6.QtWidgets import (QDialog, QHBoxLayout, QVBoxLayout, QListWidget, QScrollArea, QWidget, QMessageBox) from PySide6.QtWidgets import (QDialog, QHBoxLayout, QVBoxLayout, QListWidget, QScrollArea, QWidget, QMessageBox)
from PySide6.QtCore import Qt, QSettings, Slot from PySide6.QtCore import Qt, Slot
from gui.template_card import TemplateCard from gui.template_card import TemplateCard
from src.database_module import DatabaseManager
from gui.template_card import ORDER_STEPS # 또는 별도로 ORDER_STEPS 상수를 정의 from gui.template_card import ORDER_STEPS # 또는 별도로 ORDER_STEPS 상수를 정의
class TemplateManagementDialog(QDialog): class TemplateManagementDialog(QDialog):
def __init__(self, logger, parent=None): def __init__(self, logger, db_manager, parent=None):
super().__init__(parent) super().__init__(parent)
self.logger = logger self.logger = logger
self.setWindowTitle("템플릿 관리") self.setWindowTitle("템플릿 관리")
self.resize(800, 600) self.resize(800, 600)
self.settings = QSettings("MyCompany", "MySMSApp")
self.current_stage = 1 self.current_stage = 1
self.selected_template_id = None self.selected_template_id = None
self.db_manager = DatabaseManager() self.db_manager = db_manager
self.plus_card = None # 플러스 카드는 한 번만 생성하도록 함 self.plus_card = None # 플러스 카드는 한 번만 생성하도록 함
self.setup_ui() self.setup_ui()
self.apply_styles() self.apply_styles()
@ -80,8 +78,10 @@ class TemplateManagementDialog(QDialog):
@Slot(int) @Slot(int)
def on_step_changed(self, row: int): def on_step_changed(self, row: int):
"""주문 단계가 변경될 때 이전에 선택한 템플릿을 자동으로 선택"""
stage = row + 1 stage = row + 1
# 현재 수정 모드에 있는 카드가 있으면 수정 취소를 시도
# 현재 수정 모드에 있는 카드가 있으면 취소
if TemplateCard.currently_editing is not None: if TemplateCard.currently_editing is not None:
editing_card = TemplateCard.currently_editing editing_card = TemplateCard.currently_editing
try: try:
@ -90,13 +90,19 @@ class TemplateManagementDialog(QDialog):
self.logger.log(f"수정 모드 취소 중 오류 발생: {e}", level=1) self.logger.log(f"수정 모드 취소 중 오류 발생: {e}", level=1)
finally: finally:
TemplateCard.currently_editing = None TemplateCard.currently_editing = None
self.logger.log(f"주문 단계 변경: {stage} - {ORDER_STEPS.get(stage)}", level=1) self.logger.log(f"주문 단계 변경: {stage} - {ORDER_STEPS.get(stage)}", level=1)
# UI에 해당 단계의 템플릿 목록을 불러오기
self.load_templates_for_stage(stage) self.load_templates_for_stage(stage)
last_template = self.settings.value(f"selected_template_stage_{stage}", None)
# 해당 단계에서 마지막으로 선택한 템플릿 불러오기
last_template = self.db_manager.get_template_settings(f"selected_template_stage_{stage}")
if last_template is not None: if last_template is not None:
try: try:
last_template = int(last_template) last_template = int(last_template)
self.select_template(last_template) self.select_template(last_template) # 선택된 상태로 유지
except ValueError: except ValueError:
pass pass
@ -143,7 +149,7 @@ class TemplateManagementDialog(QDialog):
except RuntimeError: except RuntimeError:
pass pass
self.selected_template_id = template_id self.selected_template_id = template_id
self.settings.setValue(f"selected_template_stage_{self.current_stage}", template_id) self.db_manager.set_template_settings(f"selected_template_stage_{self.current_stage}", str(template_id))
self.logger.log(f"템플릿 선택됨: ID {template_id}", level=1) self.logger.log(f"템플릿 선택됨: ID {template_id}", level=1)
@Slot() @Slot()
@ -172,8 +178,17 @@ class TemplateManagementDialog(QDialog):
self.on_template_selected(template_id) self.on_template_selected(template_id)
def restore_settings(self): def restore_settings(self):
last_step = self.settings.value("last_selected_step", 1) """각 단계별 마지막으로 선택한 템플릿을 불러와 복원"""
self.step_list.setCurrentRow(int(last_step) - 1) for stage in ORDER_STEPS.keys(): # ORDER_STEPS에 있는 단계별로 조회
last_template = self.db_manager.get_template_settings(f"selected_template_stage_{stage}")
if last_template is not None:
try:
last_template = int(last_template)
if stage == self.current_stage:
self.select_template(last_template)
except ValueError:
pass
self.logger.log("템플릿 설정 복원 완료", level=1) self.logger.log("템플릿 설정 복원 완료", level=1)
def closeEvent(self, event): def closeEvent(self, event):

19
main.py
View File

@ -1,20 +1,25 @@
#!/usr/bin/env python3 # main.py
import sys import sys
import asyncio
from PySide6.QtWidgets import QApplication from PySide6.QtWidgets import QApplication
from qasync import QEventLoop # qasync 라이브러리 설치: pip install qasync
from gui.main_window import MainWindow from gui.main_window import MainWindow
from src.logger_module import Logger from src.logger_module import Logger
def main(): def main():
app = QApplication(sys.argv)
# 로거 초기화: GUI 로그 콜백은 메인창의 로그 창에 append하도록 연결 (MainWindow에서 설정)
logger = Logger(log_file="app.log") logger = Logger(log_file="app.log")
# QApplication 생성
app = QApplication(sys.argv)
# qasync 이벤트 루프 생성 및 QApplication과 통합
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
# 메인 창 생성 (여기서 logger를 전달하여 GUI 로그 출력)
window = MainWindow(logger) window = MainWindow(logger)
window.show() window.show()
sys.exit(app.exec()) with loop:
loop.run_forever()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

BIN
orders.db

Binary file not shown.

BIN
qr_code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,6 +1,6 @@
{ {
"epochs": [ { "epochs": [ {
"calculation_time": "13378378446134919", "calculation_time": "13383679415520368",
"config_version": 0, "config_version": 0,
"model_version": "0", "model_version": "0",
"padded_top_topics_start_index": 0, "padded_top_topics_start_index": 0,
@ -8,5 +8,5 @@
"top_topics_and_observing_domains": [ ] "top_topics_and_observing_domains": [ ]
} ], } ],
"hex_encoded_hmac_key": "D9AF344532865CF91C853ED77B8B69345D464E12284F8E417E2AA35C759352CF", "hex_encoded_hmac_key": "D9AF344532865CF91C853ED77B8B69345D464E12284F8E417E2AA35C759352CF",
"next_scheduled_calculation_time": "13378983246134979" "next_scheduled_calculation_time": "13384284215520418"
} }

Binary file not shown.

View File

@ -1,3 +1,3 @@
2024/12/11-17:21:28.575 61b0 Reusing MANIFEST D:\py\AUtoTao2\src\browsers\user_data\Default\Extension State/MANIFEST-000001 2025/02/11-01:49:08.970 269c Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/MANIFEST-000001
2024/12/11-17:21:28.575 61b0 Recovering log #3 2025/02/11-01:49:08.970 269c Recovering log #3
2024/12/11-17:21:28.575 61b0 Reusing old log D:\py\AUtoTao2\src\browsers\user_data\Default\Extension State/000003.log 2025/02/11-01:49:08.970 269c Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/000003.log

View File

@ -1,3 +1,3 @@
2024/12/11-17:16:24.345 4878 Reusing MANIFEST D:\py\AUtoTao2\src\browsers\user_data\Default\Extension State/MANIFEST-000001 2025/02/11-01:47:39.208 45ac Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/MANIFEST-000001
2024/12/11-17:16:24.346 4878 Recovering log #3 2025/02/11-01:47:39.209 45ac Recovering log #3
2024/12/11-17:16:24.346 4878 Reusing old log D:\py\AUtoTao2\src\browsers\user_data\Default\Extension State/000003.log 2025/02/11-01:47:39.209 45ac Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/000003.log

View File

@ -1,3 +1,3 @@
2024/12/11-17:21:30.685 6604 Reusing MANIFEST D:\py\AUtoTao2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001 2025/02/11-01:49:11.619 7110 Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001
2024/12/11-17:21:30.685 6604 Recovering log #3 2025/02/11-01:49:11.619 7110 Recovering log #3
2024/12/11-17:21:30.685 6604 Reusing old log D:\py\AUtoTao2\src\browsers\user_data\Default\GCM Store\Encryption/000003.log 2025/02/11-01:49:11.619 7110 Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/000003.log

View File

@ -1,3 +1,3 @@
2024/12/11-17:16:26.426 5efc Reusing MANIFEST D:\py\AUtoTao2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001 2025/02/11-01:47:41.821 1dc0 Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001
2024/12/11-17:16:26.426 5efc Recovering log #3 2025/02/11-01:47:41.821 1dc0 Recovering log #3
2024/12/11-17:16:26.426 5efc Reusing old log D:\py\AUtoTao2\src\browsers\user_data\Default\GCM Store\Encryption/000003.log 2025/02/11-01:47:41.821 1dc0 Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/000003.log

View File

@ -0,0 +1,3 @@
2025/02/11-01:49:09.123 269c Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\IndexedDB\https_messages.google.com_0.indexeddb.leveldb/MANIFEST-000001
2025/02/11-01:49:09.123 269c Recovering log #3
2025/02/11-01:49:09.124 269c Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\IndexedDB\https_messages.google.com_0.indexeddb.leveldb/000003.log

View File

@ -0,0 +1,3 @@
2025/02/11-01:47:39.335 45ac Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\IndexedDB\https_messages.google.com_0.indexeddb.leveldb/MANIFEST-000001
2025/02/11-01:47:39.335 45ac Recovering log #3
2025/02/11-01:47:39.335 45ac Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\IndexedDB\https_messages.google.com_0.indexeddb.leveldb/000003.log

View File

@ -1,3 +1,3 @@
2025/01/09-12:06:40.151 5a70 Reusing MANIFEST D:\py\AUtoTao2\src\browsers\user_data\Default\Local Storage\leveldb/MANIFEST-000001 2025/02/11-01:49:08.907 4174 Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Local Storage\leveldb/MANIFEST-000001
2025/01/09-12:06:40.152 5a70 Recovering log #3 2025/02/11-01:49:08.911 4174 Recovering log #3
2025/01/09-12:06:40.152 5a70 Reusing old log D:\py\AUtoTao2\src\browsers\user_data\Default\Local Storage\leveldb/000003.log 2025/02/11-01:49:08.913 4174 Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Local Storage\leveldb/000003.log

View File

@ -0,0 +1,3 @@
2025/02/11-01:47:39.138 3cb0 Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Local Storage\leveldb/MANIFEST-000001
2025/02/11-01:47:39.141 3cb0 Recovering log #3
2025/02/11-01:47:39.142 3cb0 Reusing old log D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Local Storage\leveldb/000003.log

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"sts":[{"expiry":1765440867.111811,"host":"ObTElBeSjmuk/whOMl2S5VhuM0kKnK0J1EktqFzQAJ4=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1733904867.111812},{"expiry":1765440867.130256,"host":"aso6vR2q0VkqJKMxOB1wM/2xD6W32U9MRzbLsX5k/BA=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1733904867.130258},{"expiry":1765440916.810704,"host":"nAuqgR4iEWti7SOdT3UHPl6rmZU/DeaIm38P2O2OkgA=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1733904916.810707},{"expiry":1765441288.822726,"host":"8/RrMmQlCD2Gsp14wUCE1P8r7B2C5+yE0+g79IPyRsc=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1733905288.822728},{"expiry":1765440867.106641,"host":"+loO+DGmT6DTr59JZFAnGSlBAwPkO5M/R9ec1Sw/9KA=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1733904867.106643}],"version":2} {"sts":[{"expiry":1765440867.111811,"host":"ObTElBeSjmuk/whOMl2S5VhuM0kKnK0J1EktqFzQAJ4=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1733904867.111812},{"expiry":1765440867.130256,"host":"aso6vR2q0VkqJKMxOB1wM/2xD6W32U9MRzbLsX5k/BA=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1733904867.130258},{"expiry":1770741945.03296,"host":"nAuqgR4iEWti7SOdT3UHPl6rmZU/DeaIm38P2O2OkgA=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1739205945.032963},{"expiry":1770742149.168543,"host":"8/RrMmQlCD2Gsp14wUCE1P8r7B2C5+yE0+g79IPyRsc=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1739206149.168546},{"expiry":1765440867.106641,"host":"+loO+DGmT6DTr59JZFAnGSlBAwPkO5M/R9ec1Sw/9KA=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1733904867.106643}],"version":2}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More