This commit is contained in:
parent
4520763b90
commit
1db59535fb
Binary file not shown.
15
app.log
15
app.log
|
|
@ -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
|
||||
|
|
@ -1,28 +1,49 @@
|
|||
# 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.QtCore import Qt, Slot
|
||||
from PySide6.QtCore import Qt, Slot, QSettings, QTimer
|
||||
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
|
||||
from src.sms_module import SMSMessenger # SMS 전송 모듈
|
||||
|
||||
# 메인 창 클래스
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, logger):
|
||||
super().__init__()
|
||||
self.logger = logger
|
||||
|
||||
self.settings = QSettings("When_Ride_Mycar", "SMS_Sender")
|
||||
self.db_manager = DatabaseManager() # SQLite, SQLAlchemy 기반 DB 관리자
|
||||
|
||||
self.setWindowTitle("주문 알림 SMS 전송 프로그램")
|
||||
self.resize(900, 700)
|
||||
self.setup_menu()
|
||||
self.setup_ui()
|
||||
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()
|
||||
|
||||
def start_sms_connection(self):
|
||||
asyncio.create_task(self.sms_messenger.connect())
|
||||
|
||||
def setup_menu(self):
|
||||
menu_bar = self.menuBar()
|
||||
file_menu = menu_bar.addMenu("파일")
|
||||
|
|
@ -57,10 +78,17 @@ class MainWindow(QMainWindow):
|
|||
|
||||
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.setColumnCount(6)
|
||||
self.order_table.setHorizontalHeaderLabels(["ID", "상품명", "고객명", "전화번호", "현재단계", "SMS발송여부", "최종 업데이트"])
|
||||
self.order_table.setColumnCount(8)
|
||||
self.order_table.setHorizontalHeaderLabels([
|
||||
"ID", "상품명", "고객명", "전화번호", "현재단계", "SMS발송여부", "지금발송", "최종 업데이트"
|
||||
])
|
||||
self.order_table.setToolTip("현재 진행 중인 주문 목록을 표시합니다.")
|
||||
main_layout.addWidget(QLabel("주문 목록:"))
|
||||
main_layout.addWidget(self.order_table)
|
||||
|
|
@ -73,7 +101,6 @@ class MainWindow(QMainWindow):
|
|||
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)
|
||||
|
|
@ -81,89 +108,140 @@ class MainWindow(QMainWindow):
|
|||
self.log_display.append(message)
|
||||
|
||||
def open_order_input_dialog(self):
|
||||
dialog = OrderInputDialog(self.logger, parent=self)
|
||||
dialog = OrderInputDialog(self.logger, self.db_manager, parent=self)
|
||||
if dialog.exec():
|
||||
# 주문 입력 완료 후 주문 목록 새로고침
|
||||
self.refresh_order_list()
|
||||
|
||||
def open_template_management_dialog(self):
|
||||
dialog = TemplateManagementDialog(self.logger, parent=self)
|
||||
dialog = TemplateManagementDialog(self.logger, self.db_manager, parent=self)
|
||||
dialog.exec()
|
||||
|
||||
def open_settings_dialog(self):
|
||||
dialog = SettingsDialog(self)
|
||||
dialog = SettingsDialog(self.logger, self.db_manager)
|
||||
dialog.exec()
|
||||
|
||||
def show_help(self):
|
||||
dialog = HelpDialog(parent=self)
|
||||
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):
|
||||
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 (편집 불가)
|
||||
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)
|
||||
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.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)
|
||||
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발송여부 (편집 불가)
|
||||
sms_status = "전송 완료" if order.domestic_tracking else "미전송"
|
||||
sms_item = QTableWidgetItem(sms_status)
|
||||
sms_item.setFlags(sms_item.flags() & ~Qt.ItemIsEditable)
|
||||
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 ""
|
||||
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.setItem(row, 7, update_item)
|
||||
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()
|
||||
def item_changed_slot(self):
|
||||
# 사용자가 셀을 수정한 경우, 수정 내용을 DB에 업데이트하고 확인 메시지를 보여줌
|
||||
def item_changed_slot(self, item):
|
||||
# 사용자가 셀을 수정한 경우, 수정 내용을 DB에 업데이트합니다.
|
||||
row = self.order_table.currentRow()
|
||||
if row < 0:
|
||||
return
|
||||
try:
|
||||
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()
|
||||
except Exception:
|
||||
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()
|
||||
# 기타 수정 가능한 필드는 필요에 따라 추가
|
||||
|
||||
# 수정 내용을 데이터베이스에 업데이트 (예시)
|
||||
updated_order = self.db_manager.update_order(order_id,
|
||||
order_step_text = self.order_table.item(row, 4).text()
|
||||
try:
|
||||
order_step = int(order_step_text)
|
||||
except ValueError:
|
||||
QMessageBox.warning(self, "입력 오류", "진행 단계는 정수 값으로 입력해주세요.")
|
||||
return
|
||||
updated_order = self.db_manager.update_order(
|
||||
order_id,
|
||||
customer_name=customer_name,
|
||||
product_name=product_name,
|
||||
customer_phone=customer_phone)
|
||||
# 확인 메시지 표시
|
||||
customer_phone=customer_phone,
|
||||
order_step=order_step
|
||||
)
|
||||
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()
|
||||
self.logger.log(
|
||||
f"주문서(ID {order_id}) 수정: 고객명={customer_name}, 상품명={product_name}, 전화번호={customer_phone}, 진행단계={order_step}",
|
||||
level=1
|
||||
)
|
||||
|
||||
def apply_styles(self):
|
||||
style = """
|
||||
|
|
|
|||
|
|
@ -1,145 +1,268 @@
|
|||
# gui/order_input_dialog.py (일부 발췌)
|
||||
from PySide6.QtWidgets import QDialog, QFormLayout, QLineEdit, QComboBox, QPushButton, QHBoxLayout, QVBoxLayout, QLabel
|
||||
from PySide6.QtCore import QSettings
|
||||
# gui/order_input_dialog.py
|
||||
|
||||
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):
|
||||
def __init__(self, logger, parent=None):
|
||||
def __init__(self, logger, db_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self.logger = logger
|
||||
self.db_manager = db_manager
|
||||
self.setWindowTitle("주문정보 입력")
|
||||
self.resize(600, 500)
|
||||
self.sms_messenger = SMSMessenger(headless=False, delay=1) # SMS 테스트 모듈 (필요에 따라 설정)
|
||||
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 추가
|
||||
# 1. 샵 이름 선택 ComboBox: DB에서 shop_names를 가져옴
|
||||
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)
|
||||
shop_names = self.db_manager.get_settings_by_category("shop_names")
|
||||
formatted_shop_names = [f"[{shop.tag}사업자]-{shop.value}" for shop in shop_names]
|
||||
self.shop_combo.addItems(formatted_shop_names)
|
||||
form_layout.addRow("샵 이름:", self.shop_combo)
|
||||
|
||||
# 2. 고객 이름
|
||||
self.customer_name_edit = QLineEdit()
|
||||
self.customer_name_edit.setPlaceholderText("고객 이름 입력")
|
||||
self.customer_name_edit.setToolTip("고객의 이름을 입력하세요")
|
||||
form_layout.addRow("고객 이름:", self.customer_name_edit)
|
||||
|
||||
# 3. 상품명
|
||||
self.product_name_edit = QLineEdit()
|
||||
self.product_name_edit.setPlaceholderText("상품명")
|
||||
self.product_name_edit.setToolTip("상품명을 입력하세요")
|
||||
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)
|
||||
# 4. 주문 마켓 선택 ComboBox: DB에서 order_markets를 가져옴
|
||||
self.order_market_combo = QComboBox()
|
||||
self.order_market_combo.setToolTip("주문이 발생한 국내 마켓을 선택하세요")
|
||||
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.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)
|
||||
|
||||
# 6. 타오바오 트래킹 번호
|
||||
self.taobao_tracking_edit = QLineEdit()
|
||||
self.taobao_tracking_edit.setPlaceholderText("타오바오 트래킹 번호 입력")
|
||||
self.taobao_tracking_edit.setToolTip("타오바오에서 발행된 트래킹 번호")
|
||||
form_layout.addRow("타오바오 트래킹:", self.taobao_tracking_edit)
|
||||
|
||||
# 7. 배대지 이름
|
||||
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)
|
||||
# 8. 국내 택배사 선택 ComboBox: DB에서 domestic_couriers를 가져옴
|
||||
# (기존 '선송장 택배사' 항목을 국내 택배사로 변경)
|
||||
self.domestic_courier_combo = QComboBox()
|
||||
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.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)
|
||||
# 10. 화물택배사 선택 ComboBox: DB에서 cargo_couriers를 가져옴
|
||||
self.freight_carrier_combo = QComboBox()
|
||||
self.freight_carrier_combo.setToolTip("물품이 무거울 경우 사용한 화물택배사를 선택하세요")
|
||||
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.setPlaceholderText("화물택배 트래킹 번호 입력")
|
||||
self.freight_tracking_edit.setToolTip("화물택배 전환 시 발행된 트래킹 번호")
|
||||
form_layout.addRow("화물 트래킹번호:", self.freight_tracking_edit)
|
||||
|
||||
# 12. CS 메모1
|
||||
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)
|
||||
|
||||
# 13. CS 메모2
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
# 버튼 영역
|
||||
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.complete_send_button.setToolTip("주문 정보를 저장 후 1단계 SMS를 전송합니다.")
|
||||
self.temp_save_button = QPushButton("임시저장")
|
||||
self.temp_save_button.setToolTip("주문 정보를 임시 저장합니다.")
|
||||
self.cancel_button = QPushButton("취소")
|
||||
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_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;
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,78 +1,283 @@
|
|||
# gui/settings_dialog.py
|
||||
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QListWidget, QLabel, QMessageBox
|
||||
from PySide6.QtCore import QSettings
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
|
||||
QListWidget, QLabel, QMessageBox, QTabWidget, QWidget, QComboBox
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
class SettingsDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
class ShopTagDialog(QDialog):
|
||||
"""
|
||||
샵 이름 추가 시, 사용 가능한 태그(숫자) 중 하나를 선택하는 다이얼로그.
|
||||
available_tags에는 사용되지 않은 태그 숫자 리스트가 전달됩니다.
|
||||
"""
|
||||
def __init__(self, available_tags, 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):
|
||||
self.setWindowTitle("샵 태그 선택")
|
||||
self.selected_tag = None
|
||||
layout = QVBoxLayout(self)
|
||||
label = QLabel("이 샵의 태그 번호를 선택하세요:")
|
||||
layout.addWidget(label)
|
||||
|
||||
self.shop_list = QListWidget()
|
||||
self.shop_list.setToolTip("저장된 샵 이름 목록을 확인하세요.")
|
||||
layout.addWidget(QLabel("저장된 샵 이름:"))
|
||||
layout.addWidget(self.shop_list)
|
||||
self.combo = QComboBox()
|
||||
# available_tags가 예: [3, 4, 5, 6, 7]라면 콤보박스에는 "3사", "4사", ... 로 표시
|
||||
for tag in available_tags:
|
||||
self.combo.addItem(f"{tag}사", tag)
|
||||
layout.addWidget(self.combo)
|
||||
|
||||
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)
|
||||
# 엔터키를 누르면 다이얼로그를 수락하도록 연결합니다.
|
||||
self.combo.lineEdit().returnPressed.connect(self.accept)
|
||||
|
||||
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)
|
||||
ok_button = QPushButton("확인")
|
||||
cancel_button = QPushButton("취소")
|
||||
ok_button.clicked.connect(self.accept)
|
||||
cancel_button.clicked.connect(self.reject)
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_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 exec_(self):
|
||||
result = super().exec_()
|
||||
if result == QDialog.Accepted:
|
||||
self.selected_tag = self.combo.currentData()
|
||||
return result
|
||||
|
||||
def add_shop_name(self):
|
||||
name = self.shop_input.text().strip()
|
||||
if not name:
|
||||
class SettingsDialog(QDialog):
|
||||
def __init__(self, logger, db_manager, parent=None):
|
||||
"""
|
||||
: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
|
||||
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:
|
||||
|
||||
if category == "shop_names":
|
||||
# 이미 등록된 샵 이름(및 태그)을 조회 (DB에서 tag와 value로 관리)
|
||||
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, "중복", "이미 존재하는 샵 이름입니다.")
|
||||
return
|
||||
current_names.append(name)
|
||||
self.settings.setValue("shop_names", current_names)
|
||||
self.load_shop_names()
|
||||
self.shop_input.clear()
|
||||
available = self.get_available_shop_tags(used_tags)
|
||||
tag_dialog = ShopTagDialog(available, self)
|
||||
if tag_dialog.exec_() == QDialog.Accepted:
|
||||
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):
|
||||
selected_items = self.shop_list.selectedItems()
|
||||
self.load_all_settings()
|
||||
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:
|
||||
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()
|
||||
if category == "shop_names":
|
||||
text = item.text()
|
||||
if text.startswith("[") and "]" in text:
|
||||
try:
|
||||
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;
|
||||
}
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ from PySide6.QtGui import QMouseEvent, QKeyEvent
|
|||
|
||||
# 주문 단계 목록 (하드코딩; 추후 config 등으로 분리 가능)
|
||||
ORDER_STEPS = {
|
||||
1: "주문 접수",
|
||||
2: "배송대행지 도착",
|
||||
3: "국내 도착 및 통관 시작",
|
||||
4: "통관 완료 및 국내 배송 시작",
|
||||
5: "화물 전환",
|
||||
6: "통관번호 요청",
|
||||
7: "통관번호 재요청",
|
||||
1: "통관번호 요청",
|
||||
2: "통관번호 재요청",
|
||||
3: "주문 접수",
|
||||
4: "배송대행지 도착",
|
||||
5: "국내 도착 및 통관 시작",
|
||||
6: "통관 완료 및 국내 배송 시작",
|
||||
7: "화물 전환",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ class TemplateCard(QFrame):
|
|||
self.layout.setSpacing(5)
|
||||
# 상단: 번호 필드 또는 플러스 표시
|
||||
if not self.is_plus:
|
||||
self.top_label = QLabel(f"#{self.template_id} - {self.name}")
|
||||
self.top_label = QLabel(f"# {self.name}")
|
||||
else:
|
||||
self.top_label = QLabel("+ 추가")
|
||||
self.top_label.setFixedHeight(30)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
# gui/template_management_dialog.py
|
||||
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 src.database_module import DatabaseManager
|
||||
from gui.template_card import ORDER_STEPS # 또는 별도로 ORDER_STEPS 상수를 정의
|
||||
|
||||
class TemplateManagementDialog(QDialog):
|
||||
def __init__(self, logger, parent=None):
|
||||
def __init__(self, logger, db_manager, 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.db_manager = db_manager
|
||||
self.plus_card = None # 플러스 카드는 한 번만 생성하도록 함
|
||||
self.setup_ui()
|
||||
self.apply_styles()
|
||||
|
|
@ -80,8 +78,10 @@ class TemplateManagementDialog(QDialog):
|
|||
|
||||
@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:
|
||||
|
|
@ -90,13 +90,19 @@ class TemplateManagementDialog(QDialog):
|
|||
self.logger.log(f"수정 모드 취소 중 오류 발생: {e}", level=1)
|
||||
finally:
|
||||
TemplateCard.currently_editing = None
|
||||
|
||||
self.logger.log(f"주문 단계 변경: {stage} - {ORDER_STEPS.get(stage)}", level=1)
|
||||
|
||||
# UI에 해당 단계의 템플릿 목록을 불러오기
|
||||
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:
|
||||
try:
|
||||
last_template = int(last_template)
|
||||
self.select_template(last_template)
|
||||
self.select_template(last_template) # 선택된 상태로 유지
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
|
@ -143,7 +149,7 @@ class TemplateManagementDialog(QDialog):
|
|||
except RuntimeError:
|
||||
pass
|
||||
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)
|
||||
|
||||
@Slot()
|
||||
|
|
@ -172,8 +178,17 @@ class TemplateManagementDialog(QDialog):
|
|||
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)
|
||||
"""각 단계별 마지막으로 선택한 템플릿을 불러와 복원"""
|
||||
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)
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
|
|
|||
19
main.py
19
main.py
|
|
@ -1,20 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
# main.py
|
||||
import sys
|
||||
import asyncio
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qasync import QEventLoop # qasync 라이브러리 설치: pip install qasync
|
||||
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")
|
||||
# QApplication 생성
|
||||
app = QApplication(sys.argv)
|
||||
# qasync 이벤트 루프 생성 및 QApplication과 통합
|
||||
loop = QEventLoop(app)
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# 메인 창 생성 (여기서 logger를 전달하여 GUI 로그 출력)
|
||||
window = MainWindow(logger)
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
with loop:
|
||||
loop.run_forever()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"epochs": [ {
|
||||
"calculation_time": "13378378446134919",
|
||||
"calculation_time": "13383679415520368",
|
||||
"config_version": 0,
|
||||
"model_version": "0",
|
||||
"padded_top_topics_start_index": 0,
|
||||
|
|
@ -8,5 +8,5 @@
|
|||
"top_topics_and_observing_domains": [ ]
|
||||
} ],
|
||||
"hex_encoded_hmac_key": "D9AF344532865CF91C853ED77B8B69345D464E12284F8E417E2AA35C759352CF",
|
||||
"next_scheduled_calculation_time": "13378983246134979"
|
||||
"next_scheduled_calculation_time": "13384284215520418"
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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
|
||||
2024/12/11-17:21:28.575 61b0 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 MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/MANIFEST-000001
|
||||
2025/02/11-01:49:08.970 269c Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
2024/12/11-17:16:24.346 4878 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.208 45ac Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Extension State/MANIFEST-000001
|
||||
2025/02/11-01:47:39.209 45ac Recovering log #3
|
||||
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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
2024/12/11-17:21:30.685 6604 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 MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2025/02/11-01:49:11.619 7110 Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
2024/12/11-17:16:26.426 5efc 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 MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2025/02/11-01:47:41.821 1dc0 Recovering log #3
|
||||
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
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
MANIFEST-000001
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -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/01/09-12:06:40.152 5a70 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.907 4174 Reusing MANIFEST D:\py\Mycar_SMS_Sender2\src\browsers\user_data\Default\Local Storage\leveldb/MANIFEST-000001
|
||||
2025/02/11-01:49:08.911 4174 Recovering log #3
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue