크무비 조정
This commit is contained in:
parent
0bf7a57acb
commit
ae42321b4f
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.
10890
appTranslator.log
10890
appTranslator.log
File diff suppressed because it is too large
Load Diff
|
|
@ -12,7 +12,7 @@ class BrowserController:
|
|||
self.app = app
|
||||
self.logger = logger
|
||||
self.locator_manager = locator_manager
|
||||
self.chrome_window_name = "퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome"
|
||||
# self.chrome_window_name = "퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome"
|
||||
# self.whale_window_name = "새 시크릿 탭 - Whale"
|
||||
self.chrome_hwnd = None
|
||||
self.whale_hwnd = None
|
||||
|
|
@ -22,6 +22,7 @@ class BrowserController:
|
|||
self.page = None
|
||||
|
||||
# BrowserController에 해당하는 모든 locator를 정의
|
||||
self.chrome_window_name = self.locator_manager.get_locator('BrowserControl', 'chrome_window_name')
|
||||
self.login_email_locator = self.locator_manager.get_locator('BrowserControl', 'login_email_locator')
|
||||
self.login_password_locator = self.locator_manager.get_locator('BrowserControl', 'login_password_locator')
|
||||
self.login_button_locator = self.locator_manager.get_locator('BrowserControl', 'login_button_locator')
|
||||
|
|
@ -30,6 +31,7 @@ class BrowserController:
|
|||
self.staff_login_button_locator = self.locator_manager.get_locator('BrowserControl', 'staff_login_button_locator')
|
||||
self.close_ad_dialog_locator = self.locator_manager.get_locator('BrowserControl', 'close_ad_dialog_locator')
|
||||
self.close_ad_button_locator = self.locator_manager.get_locator('BrowserControl', 'close_ad_button_locator')
|
||||
self.total_product_count_locator = self.locator_manager.get_locator('BrowserControl', 'total_product_count_locator')
|
||||
self.product_name_template = self.locator_manager.get_locator('BrowserControl', 'product_name_template')
|
||||
self.product_price_template = self.locator_manager.get_locator('BrowserControl', 'product_price_template')
|
||||
self.product_image_template = self.locator_manager.get_locator('BrowserControl', 'product_image_template')
|
||||
|
|
@ -42,8 +44,14 @@ class BrowserController:
|
|||
self.ck_source_editing_area_locator = self.locator_manager.get_locator('BrowserControl', 'ck_source_editing_area_locator')
|
||||
self.option_input_field_locator = self.locator_manager.get_locator('BrowserControl', 'option_input_field_locator')
|
||||
self.text_templates = self.locator_manager.selectors.get('DetailPageTextTemplates', {})
|
||||
self.product_name_template_xpath = self.locator_manager.get_locator('BrowserControl', 'product_name_template_xpath')
|
||||
|
||||
self.title_tab_locator = self.locator_manager.get_locator('BrowserControl', 'title_tab_locator')
|
||||
self.option_tab_locator = self.locator_manager.get_locator('BrowserControl', 'option_tab_locator')
|
||||
self.price_tab_locator = self.locator_manager.get_locator('BrowserControl', 'price_tab_locator')
|
||||
self.tag_tab_locator = self.locator_manager.get_locator('BrowserControl', 'tag_tab_locator')
|
||||
self.thumb_tab_locator = self.locator_manager.get_locator('BrowserControl', 'thumb_tab_locator')
|
||||
self.detail_tab_locator = self.locator_manager.get_locator('BrowserControl', 'detail_tab_locator')
|
||||
self.upload_tab_locator = self.locator_manager.get_locator('BrowserControl', 'upload_tab_locator')
|
||||
self.save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
|
||||
|
||||
def get_page(self):
|
||||
return self.page
|
||||
|
|
@ -133,7 +141,7 @@ class BrowserController:
|
|||
self.logger.debug('크롬 창을 찾을 수 없습니다.')
|
||||
|
||||
|
||||
async def get_total_product_count(self):
|
||||
async def get_total_product_count_ori(self):
|
||||
try:
|
||||
# JavaScript로 해당 요소의 텍스트를 가져옴
|
||||
element_text = await self.page.evaluate('''() => {
|
||||
|
|
@ -153,38 +161,58 @@ class BrowserController:
|
|||
self.logger.debug(f"상품 수를 가져오는 중 오류 발생: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
|
||||
async def get_product_name(self, index, selector='xpath'):
|
||||
"""
|
||||
상품명을 수집하는 메서드
|
||||
index : 상품명을 수집하는 인덱스
|
||||
selector : 수집방법 (css 또는 xpath)
|
||||
"""
|
||||
async def get_total_product_count(self):
|
||||
try:
|
||||
# config.ini에서 설정된 선택자에 인덱스를 적용하여 가져옴
|
||||
# product_name_selector = self.product_name_template.format(index=index)
|
||||
# self.logger.debug(f"사용된 선택자: {product_name_selector}") # 선택자 출력
|
||||
|
||||
product_name_xpath_selector = self.product_name_template_xpath.format(index=index)
|
||||
self.logger.debug(f"사용된 선택자: {product_name_xpath_selector}") # 선택자 출력
|
||||
|
||||
|
||||
# XPath 기반으로 요소 검색
|
||||
product_name_element = await self.page.locator(f"xpath={product_name_xpath_selector}").element_handle()
|
||||
|
||||
# product_name_element = await self.page.query_selector(product_name_selector)
|
||||
|
||||
# product_name_element가 None인지 확인
|
||||
if product_name_element is None:
|
||||
self.logger.error(f"상품명 요소를 찾을 수 없습니다: index {index}")
|
||||
return "수집 오류 발생"
|
||||
|
||||
# 요소가 존재할 경우, inner_text 수집
|
||||
product_name = await product_name_element.inner_text()
|
||||
return product_name.strip()
|
||||
# Python 변수를 JavaScript로 전달하여 요소의 텍스트를 가져옴
|
||||
element_text = await self.page.evaluate(f'''(selector) => {{
|
||||
let element = document.querySelector(selector);
|
||||
return element ? element.innerText : null;
|
||||
}}''', self.total_product_count_locator)
|
||||
|
||||
if element_text:
|
||||
self.logger.debug(f"가져온 텍스트: {element_text}") # 텍스트 확인용 로그
|
||||
# "총 xx개 상품"에서 숫자만 추출
|
||||
count = int(''.join(filter(str.isdigit, element_text)))
|
||||
return count
|
||||
else:
|
||||
self.logger.debug("요소를 찾을 수 없습니다.")
|
||||
return 0
|
||||
except Exception as e:
|
||||
self.logger.error(f"상품명 수집 중 오류: {e}")
|
||||
return "수집 오류 발생"
|
||||
self.logger.debug(f"상품 수를 가져오는 중 오류 발생: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
|
||||
# async def get_product_name(self, index, selector='xpath'):
|
||||
# """
|
||||
# 상품명을 수집하는 메서드
|
||||
# index : 상품명을 수집하는 인덱스
|
||||
# selector : 수집방법 (css 또는 xpath)
|
||||
# """
|
||||
# try:
|
||||
# # config.ini에서 설정된 선택자에 인덱스를 적용하여 가져옴
|
||||
# # product_name_selector = self.product_name_template.format(index=index)
|
||||
# # self.logger.debug(f"사용된 선택자: {product_name_selector}") # 선택자 출력
|
||||
|
||||
# product_name_xpath_selector = self.product_name_template_xpath.format(index=index)
|
||||
# self.logger.debug(f"사용된 선택자: {product_name_xpath_selector}") # 선택자 출력
|
||||
|
||||
|
||||
# # XPath 기반으로 요소 검색
|
||||
# product_name_element = await self.page.locator(f"xpath={product_name_xpath_selector}").element_handle()
|
||||
|
||||
# # product_name_element = await self.page.query_selector(product_name_selector)
|
||||
|
||||
# # product_name_element가 None인지 확인
|
||||
# if product_name_element is None:
|
||||
# self.logger.error(f"상품명 요소를 찾을 수 없습니다: index {index}")
|
||||
# return "수집 오류 발생"
|
||||
|
||||
# # 요소가 존재할 경우, inner_text 수집
|
||||
# product_name = await product_name_element.inner_text()
|
||||
# return product_name.strip()
|
||||
# except Exception as e:
|
||||
# self.logger.error(f"상품명 수집 중 오류: {e}")
|
||||
# return "수집 오류 발생"
|
||||
|
||||
|
||||
|
||||
|
|
@ -287,7 +315,7 @@ class BrowserController:
|
|||
self.logger.debug("세부사항 수정 및 업로드 버튼을 찾을 수 없습니다.")
|
||||
return []
|
||||
|
||||
self.logger.debug(f"수정할 상품 개수: {button_count}")
|
||||
self.logger.debug(f"현재 페이지의 수정할 상품 개수: {button_count}")
|
||||
|
||||
# 모든 버튼을 리스트로 반환
|
||||
return [buttons.nth(i) for i in range(button_count)]
|
||||
|
|
@ -333,30 +361,35 @@ class BrowserController:
|
|||
async def click_detail_tab(self):
|
||||
"""상세페이지 탭 클릭"""
|
||||
try:
|
||||
detail_tab_locator = self.locator_manager.get_locator('BrowserControl', 'detail_tab_locator')
|
||||
await self.page.click(detail_tab_locator)
|
||||
await self.page.click(self.detail_tab_locator)
|
||||
self.logger.debug("상세페이지 탭 클릭 완료.")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"상세페이지 탭 클릭 중 오류: {e}", exc_info=True)
|
||||
|
||||
async def click_option_tab(self):
|
||||
"""상세페이지 탭 클릭"""
|
||||
"""옵션 탭 클릭"""
|
||||
try:
|
||||
option_tab_locator = self.locator_manager.get_locator('BrowserControl', 'option_tab_locator')
|
||||
await self.page.click(option_tab_locator)
|
||||
await self.page.click(self.option_tab_locator)
|
||||
self.logger.debug("옵션 탭 클릭 완료.")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"옵션 탭 클릭 중 오류: {e}", exc_info=True)
|
||||
|
||||
async def click_price_tab(self):
|
||||
"""상세페이지 탭 클릭"""
|
||||
"""가격 탭 클릭"""
|
||||
try:
|
||||
price_tab_locator = self.locator_manager.get_locator('BrowserControl', 'price_tab_locator')
|
||||
await self.page.click(price_tab_locator)
|
||||
await self.page.click(self.price_tab_locator)
|
||||
self.logger.debug("가격 탭 클릭 완료.")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"가격 탭 클릭 중 오류: {e}", exc_info=True)
|
||||
|
||||
async def click_title_tab(self):
|
||||
"""상품명 탭 클릭"""
|
||||
try:
|
||||
await self.page.click(self.title_tab_locator)
|
||||
self.logger.debug("상품명 탭 클릭 완료.")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"상품명 탭 클릭 중 오류: {e}", exc_info=True)
|
||||
|
||||
async def extract_image_urls(self, optionHandler, is_option_data=False):
|
||||
"""상세페이지에서 이미지 URL 추출"""
|
||||
try:
|
||||
|
|
@ -462,8 +495,7 @@ class BrowserController:
|
|||
async def save_and_ecs_product_edit(self):
|
||||
"""상품 수정 후 저장 버튼 클릭"""
|
||||
try:
|
||||
save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
|
||||
await self.page.click(save_button_locator)
|
||||
await self.page.click(self.save_button_locator)
|
||||
await self.page.keyboard.press("Escape")
|
||||
self.logger.debug("상품 수정 내용 저장 및 ECS 완료.")
|
||||
except Exception as e:
|
||||
|
|
@ -472,8 +504,7 @@ class BrowserController:
|
|||
async def save_product_edit(self):
|
||||
"""상품 수정 후 저장 버튼 클릭"""
|
||||
try:
|
||||
save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
|
||||
await self.page.click(save_button_locator)
|
||||
await self.page.click(self.save_button_locator)
|
||||
self.logger.debug("상품 수정 내용 저장 완료.")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"저장 버튼 클릭 중 오류: {e}", exc_info=True)
|
||||
|
|
@ -482,8 +513,7 @@ class BrowserController:
|
|||
"""다음 페이지로 이동"""
|
||||
try:
|
||||
# 현재 페이지가 몇 번째 페이지인지 확인 (클래스에 'ant-pagination-item-active'가 있는 요소)
|
||||
current_page_locator = self.locator_manager.get_locator('BrowserControl', 'current_page_locator')
|
||||
current_page = await self.page.query_selector(current_page_locator)
|
||||
current_page = await self.page.query_selector(self.current_page_locator)
|
||||
|
||||
if not current_page:
|
||||
self.logger.debug("현재 페이지 정보를 찾을 수 없습니다.")
|
||||
|
|
@ -494,8 +524,7 @@ class BrowserController:
|
|||
next_page_number = current_page_number + 1
|
||||
|
||||
# 다음 페이지 버튼을 찾음 (title 속성으로 다음 페이지를 찾음)
|
||||
next_page_button_template = self.locator_manager.get_locator('BrowserControl', 'next_page_button_template')
|
||||
next_page_button_locator = next_page_button_template.format(page_number=next_page_number)
|
||||
next_page_button_locator = self.next_page_button_template.format(page_number=next_page_number)
|
||||
next_page_button = await self.page.query_selector(next_page_button_locator)
|
||||
|
||||
if next_page_button:
|
||||
|
|
@ -606,43 +635,43 @@ class BrowserController:
|
|||
self.logger.debug("최대 스크롤 횟수에 도달했습니다.")
|
||||
|
||||
|
||||
async def collect_product_info(self):
|
||||
"""
|
||||
상품 정보를 수집하는 메서드
|
||||
"""
|
||||
try:
|
||||
# 페이지를 아래로 스크롤하여 모든 상품 로드
|
||||
await self.scroll_with_wheel('down')
|
||||
await self.scroll_with_wheel('up')
|
||||
# async def collect_product_info(self):
|
||||
# """
|
||||
# 상품 정보를 수집하는 메서드
|
||||
# """
|
||||
# try:
|
||||
# # 페이지를 아래로 스크롤하여 모든 상품 로드
|
||||
# await self.scroll_with_wheel('down')
|
||||
# await self.scroll_with_wheel('up')
|
||||
|
||||
product_infos = []
|
||||
for i in range(1, 51): # 1부터 최대 50까지 상품 처리
|
||||
try:
|
||||
# 각 상품의 CSS 선택자를 동적으로 생성하여 접근
|
||||
product_name_locator = self.product_name_template.format(i=i)
|
||||
product_price_locator = self.product_price_template.format(i=i)
|
||||
product_image_locator = self.product_image_template.format(i=i)
|
||||
# product_infos = []
|
||||
# for i in range(1, 51): # 1부터 최대 50까지 상품 처리
|
||||
# try:
|
||||
# # 각 상품의 CSS 선택자를 동적으로 생성하여 접근
|
||||
# product_name_locator = self.product_name_template.format(i=i)
|
||||
# product_price_locator = self.product_price_template.format(i=i)
|
||||
# product_image_locator = self.product_image_template.format(i=i)
|
||||
|
||||
product_name_element = await self.page.query_selector(product_name_locator)
|
||||
product_price_element = await self.page.query_selector(product_price_locator)
|
||||
product_image_element = await self.page.query_selector(product_image_locator)
|
||||
# product_name_element = await self.page.query_selector(product_name_locator)
|
||||
# product_price_element = await self.page.query_selector(product_price_locator)
|
||||
# product_image_element = await self.page.query_selector(product_image_locator)
|
||||
|
||||
if product_name_element and product_price_element and product_image_element:
|
||||
product_info = {
|
||||
"name": await product_name_element.text_content().strip(),
|
||||
"price": await product_price_element.text_content().strip(),
|
||||
"image_url": await product_image_element.get_attribute('src')
|
||||
}
|
||||
self.logger.debug(f"상품 {i}: {product_info}")
|
||||
product_infos.append(product_info)
|
||||
except Exception as e:
|
||||
self.logger.error(f"상품 {i} 정보 수집 중 오류 발생: {e}", exc_info=True)
|
||||
continue
|
||||
# if product_name_element and product_price_element and product_image_element:
|
||||
# product_info = {
|
||||
# "name": await product_name_element.text_content().strip(),
|
||||
# "price": await product_price_element.text_content().strip(),
|
||||
# "image_url": await product_image_element.get_attribute('src')
|
||||
# }
|
||||
# self.logger.debug(f"상품 {i}: {product_info}")
|
||||
# product_infos.append(product_info)
|
||||
# except Exception as e:
|
||||
# self.logger.error(f"상품 {i} 정보 수집 중 오류 발생: {e}", exc_info=True)
|
||||
# continue
|
||||
|
||||
return product_infos
|
||||
except Exception as e:
|
||||
self.logger.error(f"상품 정보 수집 중 오류 발생: {e}", exc_info=True)
|
||||
return []
|
||||
# return product_infos
|
||||
# except Exception as e:
|
||||
# self.logger.error(f"상품 정보 수집 중 오류 발생: {e}", exc_info=True)
|
||||
# return []
|
||||
|
||||
|
||||
async def scroll_page_to_bottom(self, pause_time=0.2):
|
||||
|
|
|
|||
117
config.ini
117
config.ini
|
|
@ -1,19 +1,19 @@
|
|||
[PriceLocators]
|
||||
return_fee_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[3]/div/div/div/div[1]/div[2]/input
|
||||
first_delv_fee_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[4]/div/div[2]/div/div[1]/div[2]/input
|
||||
exchange_fee_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[5]/div/div/div/div[1]/div[2]/input
|
||||
plus_margin_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[2]/div/div[1]/div[8]/div/div/div[3]/div/div/div/div[1]/div[2]/input
|
||||
oversea_shipping_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[2]/div/div[1]/div[10]/div/div/div/div[1]/div[2]/input
|
||||
return_fee_input_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[3]/div/div/div/div[1]/div[2]/input'
|
||||
first_delv_fee_input_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[4]/div/div[2]/div/div[1]/div[2]/input'
|
||||
exchange_fee_input_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div/div/div[4]/div/div[1]/div[5]/div/div/div/div[1]/div[2]/input'
|
||||
plus_margin_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div/div/div[2]/div/div[1]/div[8]/div/div/div[3]/div/div/div/div[1]/div[2]/input'
|
||||
oversea_shipping_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div/div/div[2]/div/div[1]/div[10]/div/div/div/div[1]/div[2]/input'
|
||||
option_count_text_locator = 'div#productMainContentContainerId th:nth-child(2) > div > span'
|
||||
product_cost_locator = //*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{i}]/td[3]/div/div/div/div[2]/input
|
||||
standard_selling_price_locator = //*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{i}]/td[4]/div/div/div[1]/div/div[2]/input
|
||||
product_cost_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{i}]/td[3]/div/div/div/div[2]/input'
|
||||
standard_selling_price_locator = '//*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{i}]/td[4]/div/div/div[1]/div/div[2]/input'
|
||||
|
||||
[OptionLocators]
|
||||
# 옵션 관련 선택자
|
||||
option_excluded_selector_template = //*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div/div[3]
|
||||
option_input_selector_template = //*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[2]/div[1]/span/input
|
||||
single_option_locator = //div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]
|
||||
option_product_locator = //div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]
|
||||
option_excluded_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div/div[3]'
|
||||
option_input_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[2]/div[1]/span/input'
|
||||
single_option_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]'
|
||||
option_product_locator = '//div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]'
|
||||
total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
|
||||
original_name_selector_template = 'div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span'
|
||||
edit_field_selector_template = 'div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input'
|
||||
|
|
@ -23,95 +23,112 @@ price_selector_template = '#productMainContentContainerId li:nth-child({i}) sup'
|
|||
delete_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div'
|
||||
confirm_delete_button_locator = 'body > div:nth-child(18) > div > div.ant-modal-wrap.ant-modal-confirm-centered.ant-modal-centered > div > div.sc-ddjGPC.jbwEYW > div > div > div > div.ant-modal-confirm-btns > button.ant-btn.css-1li46mu.ant-btn-primary.ant-btn-dangerous'
|
||||
add_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img'
|
||||
file_input_locator = input[type="file"]
|
||||
file_input_locator = 'input[type="file"]'
|
||||
|
||||
[DetailLocators]
|
||||
product_detail_input_locator = //*[@id='detailMainContainerId']/div/div/div[{i}]/textarea
|
||||
product_image_locator = //*[@id='detailMainContainerId']/div/div/div[{i}]/img
|
||||
product_detail_input_locator = '//*[@id='detailMainContainerId']/div/div/div[{i}]/textarea'
|
||||
product_image_locator = '//*[@id='detailMainContainerId']/div/div/div[{i}]/img'
|
||||
|
||||
[DetailPageTextTemplates]
|
||||
leading_text_1 = "---"
|
||||
leading_text_2 = "# > 안녕하세요 혜리수샵입니다."
|
||||
leading_text_3 = " "
|
||||
leading_text_4 = " "
|
||||
leading_text_5 = "### 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다."
|
||||
leading_text_6 = "**반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.**"
|
||||
leading_text_7 = "---"
|
||||
leading_text_1 = '---'
|
||||
leading_text_2 = '# > 안녕하세요 혜리수샵입니다.'
|
||||
leading_text_3 = ' '
|
||||
leading_text_4 = ' '
|
||||
leading_text_5 = '### 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다.'
|
||||
leading_text_6 = '**반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.**'
|
||||
leading_text_7 = '---'
|
||||
# 필요한 만큼 추가 가능
|
||||
|
||||
[TitleLocators]
|
||||
|
||||
# 상품명 관련 선택자
|
||||
product_name_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[1]/span/input
|
||||
product_name_input_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[1]/span/input'
|
||||
product_name_input_css_path = 'div#productMainContentContainerId div:nth-child(5) > div:nth-child(1) > span > input'
|
||||
|
||||
# 상품명 추천단어 입력칸 선택자
|
||||
product_name_suggestion_input_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[1]/input
|
||||
product_name_suggestion_input_locator = '//*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[1]/input'
|
||||
product_name_suggestion_input_css_path = 'div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-affix-wrapper.css-1li46mu.ant-input-outlined > input'
|
||||
|
||||
# 상품명 추천단어 입력 검색 버튼 선택자
|
||||
product_name_search_button_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[2]/button
|
||||
product_name_search_button_locator = '//*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[2]/button'
|
||||
product_name_search_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-group-addon > button[type="button"]'
|
||||
|
||||
# 원본 상품명 선택자
|
||||
original_product_name_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[1]/div/span
|
||||
original_product_name_locator = '//*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[1]/div/span'
|
||||
original_product_name_css_path = 'div#productMainContentContainerId div.sc-aNeao.tNLFa > div.ant-flex.css-1li46mu.ant-flex-align-stretch.ant-flex-vertical > div:nth-child(1) > div > span'
|
||||
|
||||
# 상품명의 경고단어 삭제 버튼 선택자
|
||||
product_name_warning_delete_button_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[3]/div[2]/div/button
|
||||
product_name_warning_delete_button_locator = '//*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[3]/div[2]/div/button'
|
||||
product_name_warning_delete_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > div > button[type="button"]'
|
||||
|
||||
# 카테고리 관련 선택자
|
||||
category_suggestion_button_locator = //*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[2]/button
|
||||
category_suggestion_button_locator = '//*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[2]/button'
|
||||
category_suggestion_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > button[type="button"]'
|
||||
|
||||
# 카테고리 선택자 - 인증 여부에 따른 분기
|
||||
category_main_selector_with_cp = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(1)'
|
||||
category_main_selector_with_ss = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(2)'
|
||||
category_main_selector_with_esm = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(3)'
|
||||
category_certified_text_locator = div.ant-col.css-1li46mu:nth-child(1)
|
||||
category_text_with_certification_locator = div.ant-col.css-1li46mu:nth-child(2)
|
||||
category_text_without_certification_locator = div.ant-col.css-1li46mu:nth-child(1)
|
||||
category_main_selector_with_cp = 'div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=1'
|
||||
category_main_selector_with_ss = 'div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=2'
|
||||
category_main_selector_with_esm = 'div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=3'
|
||||
; category_main_selector_with_cp = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(1)'
|
||||
; category_main_selector_with_ss = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(2)'
|
||||
; category_main_selector_with_esm = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(3)'
|
||||
category_text_locator = 'div.ant-col.css-1li46mu:nth-child(1)'
|
||||
category_text_locator_certified = 'div.ant-col.css-1li46mu:nth-child(2)'
|
||||
; category_text_without_certification_locator = 'div.ant-col.css-1li46mu:nth-child(1)'
|
||||
|
||||
[BrowserControl]
|
||||
# 크롬 창 이름
|
||||
chrome_window_name = 퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome
|
||||
chrome_window_name = '퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome'
|
||||
|
||||
# 관리자 로그인 관련 선택자
|
||||
login_email_locator = input[placeholder="이메일 주소 입력"]
|
||||
login_password_locator = input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]
|
||||
login_button_locator = button:has-text("로그인 하기")
|
||||
login_email_locator = 'input[placeholder="이메일 주소 입력"]'
|
||||
login_password_locator = 'input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]'
|
||||
login_button_locator = 'button:has-text("로그인 하기")'
|
||||
|
||||
# 직원 로그인 관련 선택자
|
||||
staff_id_locator = input[placeholder="직원 아이디 입력"]
|
||||
staff_login_button_locator = button:has-text("직원 로그인 하기")
|
||||
admin_toggle_locator = button[role="switch"]
|
||||
staff_id_locator = 'input[placeholder="직원 아이디 입력"]'
|
||||
staff_login_button_locator = 'button:has-text("직원 로그인 하기")'
|
||||
admin_toggle_locator = 'button[role="switch"]'
|
||||
|
||||
# 광고 다이얼로그 관련 선택자
|
||||
close_ad_dialog_locator = div.ant-modal-wrap.ant-modal-centered
|
||||
close_ad_button_locator = div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default
|
||||
close_ad_dialog_locator = 'div.ant-modal-wrap.ant-modal-centered'
|
||||
close_ad_button_locator = 'div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default'
|
||||
|
||||
# 상품 관련 선택자
|
||||
product_name_template_xpath = /html/body/div[1]/div/div/div/div/main/div/div[2]/div[2]/div[3]/div/div/ul/div[{index}]/div/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]
|
||||
product_name_template_xpath = '/html/body/div[1]/div/div/div/div/main/div/div[2]/div[2]/div[3]/div/div/ul/div[{index}]/div/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]'
|
||||
product_name_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div.sc-ktPPKK.ezbvYT > span.sc-ecPEgm.gmiQgL.Body3Regular14.CharacterPrimary85'
|
||||
product_price_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > span.price'
|
||||
product_image_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > img'
|
||||
|
||||
# 상품 편집 및 페이지 이동 관련 선택자
|
||||
product_edit_button = button:has-text("세부사항 수정 및 업로드")
|
||||
product_edit_button_template = //button[span[text()="세부사항 수정 및 업로드"]]
|
||||
product_edit_button = 'button:has-text("세부사항 수정 및 업로드")'
|
||||
product_edit_button_template = '//button[span[text()="세부사항 수정 및 업로드"]]'
|
||||
|
||||
next_page_button_template = li.ant-pagination-item[title="{page_number}"]
|
||||
new_product_page_locator = span.ant-menu-title-content:has-text("신규 상품 등록")
|
||||
current_page_locator = li.ant-pagination-item.ant-pagination-item-active
|
||||
next_page_button_template = 'li.ant-pagination-item[title="{page_number}"]'
|
||||
new_product_page_locator = 'span.ant-menu-title-content:has-text("신규 상품 등록")'
|
||||
current_page_locator = 'li.ant-pagination-item.ant-pagination-item-active'
|
||||
|
||||
total_product_count_locator = '#root > div > div > div > div > main > div > div.sc-ezreuY.kYrYVh > div.sc-dChVcU.cRrUlt > div.sc-izQBue.dxiUJm > div > div:nth-child(1) > label > span:nth-child(2)'
|
||||
|
||||
# 편집페이지 관련 선택자
|
||||
title_tab_locator = 'div.ant-tabs-tab:has-text("상품명 / 카테고리")'
|
||||
option_tab_locator = 'div.ant-tabs-tab:has-text("옵션")'
|
||||
price_tab_locator = 'div.ant-tabs-tab:has-text("가격")'
|
||||
tag_tab_locator = 'div.ant-tabs-tab:has-text("키워드")'
|
||||
thumb_tab_locator = 'div.ant-tabs-tab:has-text("썸네일")'
|
||||
detail_tab_locator = 'div.ant-tabs-tab:has-text("상세페이지")'
|
||||
upload_tab_locator = 'div.ant-tabs-tab:has-text("업로드")'
|
||||
|
||||
# 상세페이지 소스 관련 선택자
|
||||
source_button_locator = button[data-cke-tooltip-text="소스"]
|
||||
ck_source_editing_area_locator = div.ck-source-editing-area
|
||||
source_button_locator = 'button[data-cke-tooltip-text="소스"]'
|
||||
ck_source_editing_area_locator = 'div.ck-source-editing-area'
|
||||
|
||||
# 옵션 입력 필드
|
||||
option_input_field_locator = 'div#productMainContentContainerId > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(2) > div'
|
||||
|
||||
# Save
|
||||
save_button_locator = 'button:has-text("저장하기")'
|
||||
|
||||
[CategoryMargins]
|
||||
categories = 가구, 농기구
|
||||
|
||||
|
|
|
|||
|
|
@ -17,69 +17,76 @@ class LocatorManager:
|
|||
|
||||
# PriceLocators 섹션
|
||||
self.selectors['PriceLocators'] = {
|
||||
'return_fee_input_locator': self.config.get('PriceLocators', 'return_fee_input_locator'),
|
||||
'first_delv_fee_input_locator': self.config.get('PriceLocators', 'first_delv_fee_input_locator'),
|
||||
'exchange_fee_input_locator': self.config.get('PriceLocators', 'exchange_fee_input_locator'),
|
||||
'plus_margin_locator': self.config.get('PriceLocators', 'plus_margin_locator'),
|
||||
'oversea_shipping_locator': self.config.get('PriceLocators', 'oversea_shipping_locator'),
|
||||
'option_count_text_locator': self.config.get('PriceLocators', 'option_count_text_locator'),
|
||||
'product_cost_locator': self.config.get('PriceLocators', 'product_cost_locator'),
|
||||
'standard_selling_price_locator': self.config.get('PriceLocators', 'standard_selling_price_locator')
|
||||
'return_fee_input_locator': self.config.get('PriceLocators', 'return_fee_input_locator').strip("'"),
|
||||
'first_delv_fee_input_locator': self.config.get('PriceLocators', 'first_delv_fee_input_locator').strip("'"),
|
||||
'exchange_fee_input_locator': self.config.get('PriceLocators', 'exchange_fee_input_locator').strip("'"),
|
||||
'plus_margin_locator': self.config.get('PriceLocators', 'plus_margin_locator').strip("'"),
|
||||
'oversea_shipping_locator': self.config.get('PriceLocators', 'oversea_shipping_locator').strip("'"),
|
||||
'option_count_text_locator': self.config.get('PriceLocators', 'option_count_text_locator').strip("'"),
|
||||
'product_cost_locator': self.config.get('PriceLocators', 'product_cost_locator').strip("'"),
|
||||
'standard_selling_price_locator': self.config.get('PriceLocators', 'standard_selling_price_locator').strip("'"),
|
||||
}
|
||||
|
||||
# BrowserControl 섹션
|
||||
self.selectors['BrowserControl'] = {
|
||||
'login_email_locator': self.config.get('BrowserControl', 'login_email_locator'),
|
||||
'login_password_locator': self.config.get('BrowserControl', 'login_password_locator'),
|
||||
'login_button_locator': self.config.get('BrowserControl', 'login_button_locator'),
|
||||
'admin_toggle_locator': self.config.get('BrowserControl', 'admin_toggle_locator'),
|
||||
'staff_id_locator': self.config.get('BrowserControl', 'staff_id_locator'),
|
||||
'staff_login_button_locator': self.config.get('BrowserControl', 'staff_login_button_locator'),
|
||||
'close_ad_dialog_locator': self.config.get('BrowserControl', 'close_ad_dialog_locator'),
|
||||
'close_ad_button_locator': self.config.get('BrowserControl', 'close_ad_button_locator'),
|
||||
'product_name_template': self.config.get('BrowserControl', 'product_name_template'),
|
||||
'product_price_template': self.config.get('BrowserControl', 'product_price_template'),
|
||||
'product_image_template': self.config.get('BrowserControl', 'product_image_template'),
|
||||
'new_product_page_locator': self.config.get('BrowserControl', 'new_product_page_locator'),
|
||||
'current_page_locator': self.config.get('BrowserControl', 'current_page_locator'),
|
||||
'next_page_button_template': self.config.get('BrowserControl', 'next_page_button_template'),
|
||||
'source_button_locator': self.config.get('BrowserControl', 'source_button_locator'),
|
||||
'ck_source_editing_area_locator': self.config.get('BrowserControl', 'ck_source_editing_area_locator'),
|
||||
'option_input_field_locator': self.config.get('BrowserControl', 'option_input_field_locator')
|
||||
'login_email_locator': self.config.get('BrowserControl', 'login_email_locator').strip("'"),
|
||||
'login_password_locator': self.config.get('BrowserControl', 'login_password_locator').strip("'"),
|
||||
'login_button_locator': self.config.get('BrowserControl', 'login_button_locator').strip("'"),
|
||||
'admin_toggle_locator': self.config.get('BrowserControl', 'admin_toggle_locator').strip("'"),
|
||||
'staff_id_locator': self.config.get('BrowserControl', 'staff_id_locator').strip("'"),
|
||||
'staff_login_button_locator': self.config.get('BrowserControl', 'staff_login_button_locator').strip("'"),
|
||||
'close_ad_dialog_locator': self.config.get('BrowserControl', 'close_ad_dialog_locator').strip("'"),
|
||||
'close_ad_button_locator': self.config.get('BrowserControl', 'close_ad_button_locator').strip("'"),
|
||||
'total_product_count_locator': self.config.get('BrowserControl', 'total_product_count_locator').strip("'"),
|
||||
'product_name_template': self.config.get('BrowserControl', 'product_name_template').strip("'"),
|
||||
'product_price_template': self.config.get('BrowserControl', 'product_price_template').strip("'"),
|
||||
'product_image_template': self.config.get('BrowserControl', 'product_image_template').strip("'"),
|
||||
'new_product_page_locator': self.config.get('BrowserControl', 'new_product_page_locator').strip("'"),
|
||||
'current_page_locator': self.config.get('BrowserControl', 'current_page_locator').strip("'"),
|
||||
'next_page_button_template': self.config.get('BrowserControl', 'next_page_button_template').strip("'"),
|
||||
'source_button_locator': self.config.get('BrowserControl', 'source_button_locator').strip("'"),
|
||||
'ck_source_editing_area_locator': self.config.get('BrowserControl', 'ck_source_editing_area_locator').strip("'"),
|
||||
'title_tab_locator': self.config.get('BrowserControl', 'title_tab_locator').strip("'"),
|
||||
'option_tab_locator': self.config.get('BrowserControl', 'option_tab_locator').strip("'"),
|
||||
'price_tab_locator': self.config.get('BrowserControl', 'price_tab_locator').strip("'"),
|
||||
'tag_tab_locator': self.config.get('BrowserControl', 'tag_tab_locator').strip("'"),
|
||||
'thumb_tab_locator': self.config.get('BrowserControl', 'thumb_tab_locator').strip("'"),
|
||||
'detail_tab_locator': self.config.get('BrowserControl', 'detail_tab_locator').strip("'"),
|
||||
'upload_tab_locator': self.config.get('BrowserControl', 'upload_tab_locator').strip("'"),
|
||||
'save_button_locator': self.config.get('BrowserControl', 'save_button_locator').strip("'"),
|
||||
}
|
||||
|
||||
# OptionLocators 섹션
|
||||
self.selectors['OptionLocators'] = {
|
||||
'option_excluded_selector_template': self.config.get('OptionLocators', 'option_excluded_selector_template'),
|
||||
'option_input_selector_template': self.config.get('OptionLocators', 'option_input_selector_template'),
|
||||
'single_option_locator': self.config.get('OptionLocators', 'single_option_locator'),
|
||||
'option_product_locator': self.config.get('OptionLocators', 'option_product_locator'),
|
||||
'total_options_selector': self.config.get('OptionLocators', 'total_options_selector'),
|
||||
'original_name_selector_template': self.config.get('OptionLocators', 'original_name_selector_template'),
|
||||
'edit_field_selector_template': self.config.get('OptionLocators', 'edit_field_selector_template'),
|
||||
'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template'),
|
||||
'image_selector_template': self.config.get('OptionLocators', 'image_selector_template'),
|
||||
'price_selector_template': self.config.get('OptionLocators', 'price_selector_template'),
|
||||
'delete_button_selector_template': self.config.get('OptionLocators', 'delete_button_selector_template'),
|
||||
'confirm_delete_button_locator': self.config.get('OptionLocators', 'confirm_delete_button_locator'),
|
||||
'add_button_selector_template': self.config.get('OptionLocators', 'add_button_selector_template'),
|
||||
'file_input_locator': self.config.get('OptionLocators', 'file_input_locator')
|
||||
'option_excluded_selector_template': self.config.get('OptionLocators', 'option_excluded_selector_template').strip("'"),
|
||||
'option_input_selector_template': self.config.get('OptionLocators', 'option_input_selector_template').strip("'"),
|
||||
'single_option_locator': self.config.get('OptionLocators', 'single_option_locator').strip("'"),
|
||||
'option_product_locator': self.config.get('OptionLocators', 'option_product_locator').strip("'"),
|
||||
'total_options_selector': self.config.get('OptionLocators', 'total_options_selector').strip("'"),
|
||||
'original_name_selector_template': self.config.get('OptionLocators', 'original_name_selector_template').strip("'"),
|
||||
'edit_field_selector_template': self.config.get('OptionLocators', 'edit_field_selector_template').strip("'"),
|
||||
'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template').strip("'"),
|
||||
'image_selector_template': self.config.get('OptionLocators', 'image_selector_template').strip("'"),
|
||||
'price_selector_template': self.config.get('OptionLocators', 'price_selector_template').strip("'"),
|
||||
'delete_button_selector_template': self.config.get('OptionLocators', 'delete_button_selector_template').strip("'"),
|
||||
'confirm_delete_button_locator': self.config.get('OptionLocators', 'confirm_delete_button_locator').strip("'"),
|
||||
'add_button_selector_template': self.config.get('OptionLocators', 'add_button_selector_template').strip("'"),
|
||||
'file_input_locator': self.config.get('OptionLocators', 'file_input_locator').strip("'"),
|
||||
}
|
||||
|
||||
# TitleLocators 섹션
|
||||
self.selectors['TitleLocators'] = {
|
||||
'product_name_input_locator': self.config.get('TitleLocators', 'product_name_input_locator'),
|
||||
'product_name_suggestion_input_locator': self.config.get('TitleLocators', 'product_name_suggestion_input_locator'),
|
||||
'product_name_search_button_locator': self.config.get('TitleLocators', 'product_name_search_button_locator'),
|
||||
'original_product_name_locator': self.config.get('TitleLocators', 'original_product_name_locator'),
|
||||
'product_name_warning_delete_button_locator': self.config.get('TitleLocators', 'product_name_warning_delete_button_locator'),
|
||||
'category_suggestion_button_locator': self.config.get('TitleLocators', 'category_suggestion_button_locator'),
|
||||
'category_main_selector_with_cp': self.config.get('TitleLocators', 'category_main_selector_with_cp'),
|
||||
'category_main_selector_with_ss': self.config.get('TitleLocators', 'category_main_selector_with_ss'),
|
||||
'category_main_selector_with_esm': self.config.get('TitleLocators', 'category_main_selector_with_esm'),
|
||||
'category_certified_text_locator': self.config.get('TitleLocators', 'category_certified_text_locator'),
|
||||
'category_text_with_certification_locator': self.config.get('TitleLocators', 'category_text_with_certification_locator'),
|
||||
'category_text_without_certification_locator': self.config.get('TitleLocators', 'category_text_without_certification_locator'),
|
||||
'product_name_input_locator': self.config.get('TitleLocators', 'product_name_input_locator').strip("'"),
|
||||
'product_name_suggestion_input_locator': self.config.get('TitleLocators', 'product_name_suggestion_input_locator').strip("'"),
|
||||
'product_name_search_button_locator': self.config.get('TitleLocators', 'product_name_search_button_locator').strip("'"),
|
||||
'original_product_name_locator': self.config.get('TitleLocators', 'original_product_name_locator').strip("'"),
|
||||
'product_name_warning_delete_button_locator': self.config.get('TitleLocators', 'product_name_warning_delete_button_locator').strip("'"),
|
||||
'category_suggestion_button_locator': self.config.get('TitleLocators', 'category_suggestion_button_locator').strip("'"),
|
||||
'category_main_selector_with_cp': self.config.get('TitleLocators', 'category_main_selector_with_cp').strip("'"),
|
||||
'category_main_selector_with_ss': self.config.get('TitleLocators', 'category_main_selector_with_ss').strip("'"),
|
||||
'category_main_selector_with_esm': self.config.get('TitleLocators', 'category_main_selector_with_esm').strip("'"),
|
||||
'category_text_locator': self.config.get('TitleLocators', 'category_text_locator').strip("'"),
|
||||
'category_text_locator_certified': self.config.get('TitleLocators', 'category_text_locator_certified').strip("'"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
418
src/cmb_diag.py
418
src/cmb_diag.py
|
|
@ -1,7 +1,7 @@
|
|||
from PySide6.QtWidgets import (QDialog, QFileDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QTreeWidget, QTreeWidgetItem, QLabel, QLineEdit, QPushButton,
|
||||
QSpinBox, QGroupBox, QFileDialog, QMessageBox, QCheckBox)
|
||||
QSpinBox, QGroupBox, QFileDialog, QMessageBox, QComboBox)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtGui import QFont, QColor
|
||||
import sqlite3
|
||||
import shutil
|
||||
import os, re
|
||||
|
|
@ -32,12 +32,12 @@ class CMBSettingsDialog(QDialog):
|
|||
|
||||
left_layout = QVBoxLayout()
|
||||
mid_layout = QVBoxLayout()
|
||||
right_layout = QVBoxLayout()
|
||||
self.right_layout = QVBoxLayout()
|
||||
|
||||
|
||||
# 카테고리 검색 및 단계 설정 버튼
|
||||
search_layout = QHBoxLayout()
|
||||
search_layout.addWidget(QLabel("카테고리 검색:"))
|
||||
search_layout = QGridLayout()
|
||||
search_layout.addWidget(QLabel("카테고리 검색:"),0,0,1,1)
|
||||
self.search_input = QLineEdit()
|
||||
self.search_input.returnPressed.connect(self.search_category)
|
||||
self.search_btn = QPushButton("검색")
|
||||
|
|
@ -46,23 +46,69 @@ class CMBSettingsDialog(QDialog):
|
|||
self.search_btn.setDefault(False)
|
||||
|
||||
self.search_btn.clicked.connect(self.search_category)
|
||||
search_layout.addWidget(self.search_input)
|
||||
search_layout.addWidget(self.search_btn)
|
||||
search_layout.addWidget(self.search_input,0,1,1,3)
|
||||
search_layout.addWidget(self.search_btn,0,2,1,1)
|
||||
self.cmb_view_btn = QPushButton("모두 보기")
|
||||
self.cmb_view_btn.clicked.connect(self.toggle_cmb_view)
|
||||
search_layout.addWidget(self.cmb_view_btn)
|
||||
search_layout.addWidget(self.cmb_view_btn,0,3,1,1)
|
||||
|
||||
|
||||
# 1레벨, 2레벨, 3레벨 콤보박스와 라벨 설정
|
||||
self.level1_combo = QComboBox()
|
||||
self.level2_combo = QComboBox()
|
||||
self.level3_combo = QComboBox()
|
||||
self.reset_combo_btn = QPushButton("콤보리셋")
|
||||
|
||||
# 기본적으로 "모두 보기" 옵션 추가
|
||||
self.level1_combo.addItem("모두 보기")
|
||||
self.level2_combo.addItem("모두 보기")
|
||||
self.level3_combo.addItem("모두 보기")
|
||||
|
||||
# 레벨 필터링 라벨 추가
|
||||
# search_layout.addWidget(QLabel("1레벨:"))
|
||||
search_layout.addWidget(self.level1_combo,1,1,1,2)
|
||||
# search_layout.addWidget(QLabel("2레벨:"))
|
||||
search_layout.addWidget(self.level2_combo,1,2,1,2)
|
||||
# search_layout.addWidget(QLabel("3레벨:"))
|
||||
search_layout.addWidget(self.level3_combo,1,3,1,2)
|
||||
search_layout.addWidget(self.level3_combo,1,3,1,2)
|
||||
search_layout.addWidget(self.reset_combo_btn,1,4,1,1)
|
||||
|
||||
# 콤보박스의 신호 연결
|
||||
self.level1_combo.currentTextChanged.connect(self.update_level2_combo)
|
||||
self.level1_combo.currentTextChanged.connect(self.filter_category_tree)
|
||||
self.level2_combo.currentTextChanged.connect(self.update_level3_combo)
|
||||
self.level2_combo.currentTextChanged.connect(self.filter_category_tree)
|
||||
self.level3_combo.currentTextChanged.connect(self.filter_category_tree)
|
||||
|
||||
# 콤보박스 리셋
|
||||
self.reset_combo_btn.clicked.connect(self.reset_comboboxes)
|
||||
|
||||
# # 카테고리 레벨별 필터링을 위한 콤보박스
|
||||
# filter_label = QLabel("카테고리 필터:")
|
||||
# filter_combo = QComboBox()
|
||||
# filter_combo.addItem("모두 보기")
|
||||
# filter_combo.addItem("Level 1")
|
||||
# filter_combo.addItem("Level 2")
|
||||
# filter_combo.addItem("Level 3")
|
||||
# filter_combo.addItem("Level 4")
|
||||
# filter_combo.currentIndexChanged.connect(self.filter_categories)
|
||||
# search_layout.addWidget(filter_label,1,0,1,1)
|
||||
# search_layout.addWidget(filter_combo)
|
||||
|
||||
|
||||
left_layout.addLayout(search_layout, 1)
|
||||
|
||||
# 카테고리 목록 테이블
|
||||
self.category_tree = QTreeWidget()
|
||||
self.category_tree.setHeaderLabels(["Level1", "Level2", "Level3", "Level4", "CMB 단계"])
|
||||
self.category_tree.setColumnCount(5)
|
||||
self.category_tree.setHeaderLabels(["ID", "Level1", "Level2", "Level3", "Level4", "CMB 단계"])
|
||||
self.category_tree.setColumnCount(6)
|
||||
self.category_tree.setRootIsDecorated(False)
|
||||
self.category_tree.setAlternatingRowColors(True)
|
||||
self.category_tree.setSortingEnabled(True) # 정렬 기능 활성화
|
||||
# 정렬 순서 추적
|
||||
self.sort_order = Qt.AscendingOrder
|
||||
|
||||
# 헤더 클릭 시그널 연결
|
||||
self.category_tree.header().sectionClicked.connect(self.sort_by_column)
|
||||
|
||||
|
|
@ -83,12 +129,21 @@ class CMBSettingsDialog(QDialog):
|
|||
apply_1_btn = QPushButton("1단계 적용")
|
||||
apply_2_btn = QPushButton("2단계 적용")
|
||||
apply_3_btn = QPushButton("3단계 적용")
|
||||
remove_cmb_stage_button = QPushButton("선택 CMB 해제")
|
||||
|
||||
self.select_toggle_button = QPushButton("전체 선택")
|
||||
|
||||
apply_1_btn.clicked.connect(lambda: self.apply_crmobi_stage(1))
|
||||
apply_2_btn.clicked.connect(lambda: self.apply_crmobi_stage(2))
|
||||
apply_3_btn.clicked.connect(lambda: self.apply_crmobi_stage(3))
|
||||
remove_cmb_stage_button.clicked.connect(self.remove_cmb_stage)
|
||||
self.select_toggle_button.clicked.connect(self.toggle_select_all_filtered_items)
|
||||
|
||||
mid_layout.addWidget(apply_1_btn)
|
||||
mid_layout.addWidget(apply_2_btn)
|
||||
mid_layout.addWidget(apply_3_btn)
|
||||
mid_layout.addWidget(remove_cmb_stage_button)
|
||||
mid_layout.addWidget(self.select_toggle_button)
|
||||
|
||||
# 닫기 버튼
|
||||
close_btn = QPushButton("닫기")
|
||||
|
|
@ -105,8 +160,14 @@ class CMBSettingsDialog(QDialog):
|
|||
|
||||
# CMB 단계 설정 그룹 추가
|
||||
self.cmb_settings_group = QGroupBox("CMB 단계 설정")
|
||||
|
||||
cmb_settings_layout = QVBoxLayout()
|
||||
|
||||
# CMB 단계를 설정 후 저장하는 버튼
|
||||
save_cmb_stage_button = QPushButton("CMB 단계 저장")
|
||||
save_cmb_stage_button.clicked.connect(self.save_cmb_stage_to_db)
|
||||
cmb_settings_layout.addWidget(save_cmb_stage_button)
|
||||
|
||||
# 각 단계별 설정
|
||||
self.stage_widgets = []
|
||||
for i in range(1, 4):
|
||||
|
|
@ -154,12 +215,12 @@ class CMBSettingsDialog(QDialog):
|
|||
cost_spin.setSingleStep(1000)
|
||||
|
||||
# QGroupBox 레이아웃 구성
|
||||
stage_group_box_layout.addWidget(min_amount_spin,0,0)
|
||||
stage_group_box_layout.addWidget(min_amount_label,0,1)
|
||||
stage_group_box_layout.addWidget(unit_amount_spin,1,0)
|
||||
stage_group_box_layout.addWidget(unit_amount_label,1,1)
|
||||
stage_group_box_layout.addWidget(cost_spin,2,0)
|
||||
stage_group_box_layout.addWidget(cost_label,2,1)
|
||||
stage_group_box_layout.addWidget(min_amount_spin,0,0,1,2)
|
||||
stage_group_box_layout.addWidget(min_amount_label,1,1)
|
||||
stage_group_box_layout.addWidget(unit_amount_spin,2,0,1,2)
|
||||
stage_group_box_layout.addWidget(unit_amount_label,3,1)
|
||||
stage_group_box_layout.addWidget(cost_spin,4,0,1,2)
|
||||
stage_group_box_layout.addWidget(cost_label,5,1)
|
||||
stage_group_box.setLayout(stage_group_box_layout)
|
||||
|
||||
# 전체 레이아웃에 QGroupBox 추가
|
||||
|
|
@ -169,13 +230,144 @@ class CMBSettingsDialog(QDialog):
|
|||
self.stage_widgets.append((min_amount_spin, unit_amount_spin, cost_spin))
|
||||
|
||||
self.cmb_settings_group.setLayout(cmb_settings_layout)
|
||||
right_layout.addWidget(self.cmb_settings_group)
|
||||
main_layout.addLayout(right_layout,2)
|
||||
self.right_layout.addWidget(self.cmb_settings_group)
|
||||
main_layout.addLayout(self.right_layout,3)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
|
||||
# DB 읽어와서 테이블에 표시
|
||||
self.load_db_to_table()
|
||||
self.set_column_widths()
|
||||
self.update_cmb_settings_from_db()
|
||||
self.load_level1_categories()
|
||||
|
||||
def load_level1_categories(self):
|
||||
"""1레벨 카테고리를 DB에서 로드하여 콤보박스에 추가"""
|
||||
query = "SELECT DISTINCT category1 FROM categories WHERE category1 IS NOT NULL"
|
||||
self.cursor.execute(query)
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
for row in rows:
|
||||
self.level1_combo.addItem(row[0])
|
||||
|
||||
def update_level2_combo(self):
|
||||
"""1레벨 선택 시, 2레벨 콤보박스를 업데이트"""
|
||||
selected_level1 = self.level1_combo.currentText()
|
||||
self.level2_combo.clear()
|
||||
self.level2_combo.addItem("모두 보기")
|
||||
|
||||
if selected_level1 == "모두 보기":
|
||||
return
|
||||
|
||||
query = "SELECT DISTINCT category2 FROM categories WHERE category1 = ? AND category2 IS NOT NULL"
|
||||
self.cursor.execute(query, (selected_level1,))
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
for row in rows:
|
||||
self.level2_combo.addItem(row[0])
|
||||
|
||||
def update_level3_combo(self):
|
||||
"""2레벨 선택 시, 3레벨 콤보박스를 업데이트"""
|
||||
selected_level1 = self.level1_combo.currentText()
|
||||
selected_level2 = self.level2_combo.currentText()
|
||||
self.level3_combo.clear()
|
||||
self.level3_combo.addItem("모두 보기")
|
||||
|
||||
if selected_level2 == "모두 보기":
|
||||
return
|
||||
|
||||
query = "SELECT DISTINCT category3 FROM categories WHERE category1 = ? AND category2 = ? AND category3 IS NOT NULL"
|
||||
self.cursor.execute(query, (selected_level1, selected_level2))
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
for row in rows:
|
||||
self.level3_combo.addItem(row[0])
|
||||
|
||||
def reset_comboboxes(self):
|
||||
"""1레벨, 2레벨, 3레벨 콤보박스를 초기화하고 QTreeWidget을 전체 항목으로 필터링합니다."""
|
||||
# 각 레벨 콤보박스 초기화
|
||||
self.level1_combo.setCurrentIndex(0)
|
||||
self.level2_combo.clear()
|
||||
self.level3_combo.clear()
|
||||
|
||||
# QTreeWidget 필터링 초기화 (모든 항목 표시)
|
||||
self.load_db_to_table() # 이 메서드는 전체 데이터를 다시 로드하는 메서드입니다.
|
||||
|
||||
def filter_category_tree(self):
|
||||
"""선택된 1레벨, 2레벨, 3레벨 카테고리를 기준으로 트리뷰를 필터링"""
|
||||
selected_level1 = self.level1_combo.currentText()
|
||||
selected_level2 = self.level2_combo.currentText()
|
||||
selected_level3 = self.level3_combo.currentText()
|
||||
|
||||
# 기본 쿼리와 조건을 설정
|
||||
query = '''SELECT id, category1, category2, category3, category4, crmobi_stage FROM categories WHERE 1=1'''
|
||||
args = []
|
||||
|
||||
# 선택된 값에 따라 필터 추가
|
||||
if selected_level1 != "모두 보기":
|
||||
query += " AND category1 = ?"
|
||||
args.append(selected_level1)
|
||||
if selected_level2 != "모두 보기":
|
||||
query += " AND category2 = ?"
|
||||
args.append(selected_level2)
|
||||
if selected_level3 != "모두 보기":
|
||||
query += " AND category3 = ?"
|
||||
args.append(selected_level3)
|
||||
|
||||
self.cursor.execute(query, args)
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
# 트리뷰에 표시
|
||||
self.category_tree.clear()
|
||||
for row_data in rows:
|
||||
row_id, category1, category2, category3, category4, crmobi_stage = row_data
|
||||
formatted_id = str(row_id).zfill(4)
|
||||
top_item = QTreeWidgetItem([
|
||||
formatted_id,
|
||||
category1 or "", category2 or "",
|
||||
category3 or "", category4 or "",
|
||||
f"{crmobi_stage}" if crmobi_stage > 0 else "미적용"
|
||||
])
|
||||
top_item.setCheckState(0, Qt.Unchecked)
|
||||
|
||||
# 단계별 배경색 설정 QColor(R,G,B,A)
|
||||
if crmobi_stage == 1:
|
||||
color = QColor(152, 251, 152, 204) # 옐로우그린
|
||||
elif crmobi_stage == 2:
|
||||
color = QColor(135, 206, 235, 204) # 스카이블루
|
||||
elif crmobi_stage == 3:
|
||||
color = QColor(255, 192, 203, 204) # 라이트핑크
|
||||
else:
|
||||
color = None
|
||||
|
||||
# 행 전체에 배경색 적용
|
||||
if color:
|
||||
for col in range(6): # 열의 개수에 따라 반복
|
||||
top_item.setBackground(col, color)
|
||||
|
||||
self.category_tree.addTopLevelItem(top_item)
|
||||
|
||||
|
||||
def update_cmb_settings_from_db(self):
|
||||
"""DB의 crmobi_stages 테이블에서 값을 읽어와 각 단계별 설정 위젯에 반영합니다."""
|
||||
try:
|
||||
# CrMoBi 단계 설정을 crmobi_stages 테이블에서 가져옴
|
||||
self.cursor.execute("SELECT stage, threshold, increment_unit, extra_cost FROM crmobi_stages")
|
||||
stages = self.cursor.fetchall()
|
||||
|
||||
# 각 단계별 설정 값을 위젯에 적용
|
||||
for stage in stages:
|
||||
stage_index = stage[0] - 1 # 단계가 1부터 시작하므로 인덱스를 맞추기 위해 -1
|
||||
min_amount, unit_amount, extra_cost = stage[1], stage[2], stage[3]
|
||||
|
||||
# 위젯 리스트에서 해당 단계를 찾아서 설정
|
||||
self.stage_widgets[stage_index][0].setValue(min_amount)
|
||||
self.stage_widgets[stage_index][1].setValue(unit_amount)
|
||||
self.stage_widgets[stage_index][2].setValue(extra_cost)
|
||||
|
||||
self.logger.debug("CrMoBi 단계 설정이 위젯에 반영되었습니다.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"CrMoBi 단계 설정을 위젯에 반영하는 중 오류 발생: {e}", exc_info=True)
|
||||
|
||||
def create_tables(self):
|
||||
"""초기 DB를 생성하고 CrMoBi 단계 테이블도 추가합니다."""
|
||||
|
|
@ -191,6 +383,7 @@ class CMBSettingsDialog(QDialog):
|
|||
# 테이블 생성
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
category1 TEXT,
|
||||
category2 TEXT,
|
||||
category3 TEXT,
|
||||
|
|
@ -237,6 +430,9 @@ class CMBSettingsDialog(QDialog):
|
|||
''')
|
||||
|
||||
# 초기 데이터 설정 (원하는 경우)
|
||||
cursor.execute('DELETE FROM crmobi_stages')
|
||||
conn.commit()
|
||||
|
||||
for i in range(1, 4):
|
||||
cursor.execute('''
|
||||
INSERT INTO crmobi_stages (stage, threshold, increment_unit, extra_cost)
|
||||
|
|
@ -254,7 +450,7 @@ class CMBSettingsDialog(QDialog):
|
|||
self.category_tree.clear()
|
||||
|
||||
# 기본 쿼리와 조건을 설정
|
||||
query = '''SELECT category1, category2, category3, category4, crmobi_stage FROM categories WHERE 1=1'''
|
||||
query = '''SELECT id, category1, category2, category3, category4, crmobi_stage FROM categories WHERE 1=1'''
|
||||
args = []
|
||||
|
||||
# 검색어가 있을 경우 WHERE 조건 추가
|
||||
|
|
@ -269,65 +465,152 @@ class CMBSettingsDialog(QDialog):
|
|||
|
||||
self.cursor.execute(query, args)
|
||||
rows = self.cursor.fetchall()
|
||||
|
||||
for row_data in rows:
|
||||
top_item = QTreeWidgetItem([str(data) if data else "" for data in row_data[:4]])
|
||||
crmobi_stage = row_data[4]
|
||||
top_item.setCheckState(0, Qt.Unchecked)
|
||||
top_item.setText(4, f"{crmobi_stage}" if crmobi_stage > 0 else "미적용")
|
||||
|
||||
# 단계별 배경색 설정
|
||||
# id 기준 오름차순 정렬을 위해 정렬
|
||||
rows = sorted(rows, key=lambda x: int(x[0])) # x[0]는 id 열
|
||||
|
||||
for row_data in rows:
|
||||
row_id, category1, category2, category3, category4, crmobi_stage = row_data
|
||||
formatted_id = str(row_id).zfill(4)
|
||||
top_item = QTreeWidgetItem([formatted_id, category1 or "", category2 or "", category3 or "", category4 or "", f"{crmobi_stage}" if crmobi_stage > 0 else "미적용"])
|
||||
top_item.setCheckState(0, Qt.Unchecked)
|
||||
|
||||
# id 값을 정수로 설정하여 정렬 시 정수 기준으로 처리되도록 함
|
||||
top_item.setData(0, Qt.UserRole, int(row_id))
|
||||
|
||||
# 단계별 배경색 설정 QColor(R,G,B,A)
|
||||
if crmobi_stage == 1:
|
||||
top_item.setBackground(4, Qt.yellow)
|
||||
color = QColor(152, 251, 152, 204) # 옐로우그린
|
||||
elif crmobi_stage == 2:
|
||||
top_item.setBackground(4, Qt.green)
|
||||
color = QColor(135, 206, 235, 204) # 스카이블루
|
||||
elif crmobi_stage == 3:
|
||||
top_item.setBackground(4, Qt.red)
|
||||
|
||||
color = QColor(255, 192, 203, 204) # 라이트핑크
|
||||
else:
|
||||
color = None
|
||||
|
||||
# 행 전체에 배경색 적용
|
||||
if color:
|
||||
for col in range(6): # 열의 개수에 따라 반복
|
||||
top_item.setBackground(col, color)
|
||||
|
||||
self.category_tree.addTopLevelItem(top_item)
|
||||
|
||||
# 초기 정렬 기준을 id 열로 설정하고 오름차순 정렬
|
||||
self.category_tree.setSortingEnabled(True)
|
||||
self.category_tree.sortByColumn(0, Qt.AscendingOrder) # ID 열을 기준으로 오름차순 정렬
|
||||
|
||||
def set_column_widths(self):
|
||||
"""ID 열 너비를 일정하게 설정"""
|
||||
current_id_width = self.category_tree.columnWidth(0)
|
||||
self.category_tree.setColumnWidth(0, int(current_id_width / 1.5)) # ID 열을 초기 설정 크기로 유지
|
||||
|
||||
|
||||
def sort_by_column(self, index):
|
||||
"""클릭된 열을 기준으로 오름차순/내림차순으로 정렬"""
|
||||
self.category_tree.sortByColumn(index, self.sort_order)
|
||||
|
||||
# 정렬 역할을 UserRole로 설정하여 ID 필드가 정수로 정렬되도록 설정
|
||||
self.category_tree.setSortingEnabled(False) # 정렬을 일시적으로 비활성화
|
||||
self.category_tree.sortItems(index, self.sort_order)
|
||||
self.category_tree.setSortingEnabled(True) # 정렬을 다시 활성화
|
||||
|
||||
# 정렬 순서를 토글
|
||||
self.sort_order = Qt.DescendingOrder if self.sort_order == Qt.AscendingOrder else Qt.AscendingOrder
|
||||
|
||||
def apply_crmobi_stage(self, stage):
|
||||
"""선택된 카테고리에 CrMoBi 단계를 적용합니다."""
|
||||
"""선택된 카테고리에 CrMoBi 단계를 적용하고 DB에 저장."""
|
||||
for i in range(self.category_tree.topLevelItemCount()):
|
||||
item = self.category_tree.topLevelItem(i)
|
||||
if item.checkState(0) == Qt.Checked:
|
||||
category_values = [item.text(j) for j in range(4)]
|
||||
|
||||
category_values = [item.text(j) for j in range(1, 5)] # ID 열 제외
|
||||
# DB 업데이트
|
||||
self.cursor.execute('''UPDATE categories
|
||||
SET crmobi_stage = ?
|
||||
WHERE category1 = ? AND category2 = ? AND category3 = ? AND category4 = ?''',
|
||||
SET crmobi_stage = ?
|
||||
WHERE category1 = ? AND category2 = ? AND category3 = ? AND category4 = ?''',
|
||||
[stage] + category_values)
|
||||
|
||||
# 트리뷰 업데이트
|
||||
item.setText(4, str(stage))
|
||||
if stage == 1:
|
||||
item.setBackground(4, Qt.yellow)
|
||||
elif stage == 2:
|
||||
item.setBackground(4, Qt.green)
|
||||
elif stage == 3:
|
||||
item.setBackground(4, Qt.red)
|
||||
|
||||
self.conn.commit() # 변경사항 저장
|
||||
self.load_db_to_table() # 트리 새로고침
|
||||
|
||||
def toggle_cmb_settings(self, checked):
|
||||
"""CMB 단계 설정 영역 표시/숨기기"""
|
||||
self.cmb_settings_group.setVisible(checked)
|
||||
|
||||
def remove_cmb_stage(self):
|
||||
"""선택된 카테고리의 CMB 단계를 해제하고 DB에 반영."""
|
||||
for i in range(self.category_tree.topLevelItemCount()):
|
||||
item = self.category_tree.topLevelItem(i)
|
||||
if item.checkState(0) == Qt.Checked:
|
||||
category_id = int(item.text(0)) # ID 열에서 값 가져오기
|
||||
# DB에서 CMB 단계를 해제
|
||||
self.cursor.execute("UPDATE categories SET crmobi_stage = 0 WHERE id = ?", (category_id,))
|
||||
|
||||
self.conn.commit()
|
||||
self.load_db_to_table() # 트리 새로고침
|
||||
|
||||
def toggle_cmb_settings(self, checked):
|
||||
"""CMB 단계 설정 영역 표시/숨기기, 공간을 완전히 제거 또는 복원합니다."""
|
||||
sender = self.sender()
|
||||
|
||||
if checked:
|
||||
# 공간에 cmb_settings_group 추가
|
||||
self.right_layout.addWidget(self.cmb_settings_group)
|
||||
self.cmb_settings_group.show()
|
||||
sender.setText("CMB 단계 설정 ▼")
|
||||
else:
|
||||
# cmb_settings_group 숨기기 및 제거
|
||||
self.cmb_settings_group.hide()
|
||||
self.right_layout.removeWidget(self.cmb_settings_group)
|
||||
sender.setText("CMB 단계 설정 ▶")
|
||||
|
||||
|
||||
# 레이아웃을 다시 갱신하여 공간을 완전히 반영
|
||||
self.layout().update()
|
||||
def toggle_select_all_filtered_items(self):
|
||||
"""버튼의 텍스트에 따라 전체 선택 또는 전체 해제를 수행합니다."""
|
||||
if self.select_toggle_button.text() == "전체 선택":
|
||||
# 전체 체크
|
||||
for i in range(self.category_tree.topLevelItemCount()):
|
||||
item = self.category_tree.topLevelItem(i)
|
||||
item.setCheckState(0, Qt.Checked)
|
||||
# 버튼 텍스트를 "전체 해제"로 변경
|
||||
self.select_toggle_button.setText("전체 해제")
|
||||
else:
|
||||
# 전체 체크 해제
|
||||
for i in range(self.category_tree.topLevelItemCount()):
|
||||
item = self.category_tree.topLevelItem(i)
|
||||
item.setCheckState(0, Qt.Unchecked)
|
||||
# 버튼 텍스트를 "전체 선택"으로 변경
|
||||
self.select_toggle_button.setText("전체 선택")
|
||||
|
||||
def save_cmb_stage_to_db(self):
|
||||
"""사용자가 설정한 CMB 단계를 crmobi_stage 테이블에 저장합니다."""
|
||||
try:
|
||||
# 각 CMB 단계의 설정을 불러옵니다
|
||||
for i, (min_amount_spin, unit_amount_spin, cost_spin) in enumerate(self.stage_widgets, start=1):
|
||||
stage = i
|
||||
threshold = min_amount_spin.value()
|
||||
increment_unit = unit_amount_spin.value()
|
||||
extra_cost = cost_spin.value()
|
||||
|
||||
# 기존 데이터가 있는지 확인
|
||||
self.cursor.execute("SELECT COUNT(1) FROM crmobi_stages WHERE stage = ?", (stage,))
|
||||
exists = self.cursor.fetchone()[0]
|
||||
|
||||
# 데이터를 업데이트 또는 삽입
|
||||
if exists:
|
||||
self.cursor.execute('''
|
||||
UPDATE crmobi_stages
|
||||
SET threshold = ?, increment_unit = ?, extra_cost = ?
|
||||
WHERE stage = ?
|
||||
''', (threshold, increment_unit, extra_cost, stage))
|
||||
else:
|
||||
self.cursor.execute('''
|
||||
INSERT INTO crmobi_stages (stage, threshold, increment_unit, extra_cost)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (stage, threshold, increment_unit, extra_cost))
|
||||
|
||||
# 변경사항 저장
|
||||
self.conn.commit()
|
||||
QMessageBox.information(self, "저장 성공", "CMB 단계 설정이 성공적으로 저장되었습니다.")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "저장 오류", f"저장 중 오류가 발생했습니다: {e}")
|
||||
self.logger.error(f"CMB 단계 저장 중 오류: {e}", exc_info=True)
|
||||
|
||||
def reset_db(self):
|
||||
"""사용자 DB를 삭제하고 초기 DB를 로드합니다."""
|
||||
if os.path.exists(self.user_db_path):
|
||||
|
|
@ -386,7 +669,7 @@ class CMBSettingsDialog(QDialog):
|
|||
if search_text:
|
||||
# 검색어가 있는 경우 필터링하여 로드
|
||||
query = '''
|
||||
SELECT category1, category2, category3, category4, crmobi_stage
|
||||
SELECT id, category1, category2, category3, category4, crmobi_stage
|
||||
FROM categories
|
||||
WHERE category1 LIKE ? OR category2 LIKE ? OR category3 LIKE ? OR category4 LIKE ?
|
||||
'''
|
||||
|
|
@ -395,7 +678,7 @@ class CMBSettingsDialog(QDialog):
|
|||
else:
|
||||
# 검색어가 없는 경우 전체 로드
|
||||
query = '''
|
||||
SELECT category1, category2, category3, category4, crmobi_stage
|
||||
SELECT id, category1, category2, category3, category4, crmobi_stage
|
||||
FROM categories
|
||||
'''
|
||||
self.cursor.execute(query)
|
||||
|
|
@ -405,18 +688,29 @@ class CMBSettingsDialog(QDialog):
|
|||
rows = self.cursor.fetchall()
|
||||
|
||||
for row_data in rows:
|
||||
top_item = QTreeWidgetItem([str(data) if data else "" for data in row_data[:4]])
|
||||
crmobi_stage = row_data[4]
|
||||
top_item.setCheckState(0, Qt.Unchecked)
|
||||
top_item.setText(4, f"{crmobi_stage}" if crmobi_stage > 0 else "미적용")
|
||||
# top_item = QTreeWidgetItem([str(data) if data else "" for data in row_data[:4]])
|
||||
row_id, category1, category2, category3, category4, crmobi_stage = row_data
|
||||
formatted_id = str(row_id).zfill(4)
|
||||
top_item = QTreeWidgetItem([formatted_id, category1 or "", category2 or "", category3 or "", category4 or "", f"{crmobi_stage}" if crmobi_stage > 0 else "미적용"])
|
||||
|
||||
# 단계별 배경색 설정
|
||||
crmobi_stage = row_data[5]
|
||||
top_item.setCheckState(0, Qt.Unchecked)
|
||||
top_item.setText(5, f"{crmobi_stage}" if crmobi_stage > 0 else "미적용")
|
||||
|
||||
# 단계별 배경색 설정 QColor(R,G,B,A)
|
||||
if crmobi_stage == 1:
|
||||
top_item.setBackground(4, Qt.yellow)
|
||||
color = QColor(152, 251, 152, 204) # 옐로우그린
|
||||
elif crmobi_stage == 2:
|
||||
top_item.setBackground(4, Qt.green)
|
||||
color = QColor(135, 206, 235, 204) # 스카이블루
|
||||
elif crmobi_stage == 3:
|
||||
top_item.setBackground(4, Qt.red)
|
||||
color = QColor(255, 192, 203, 204) # 라이트핑크
|
||||
else:
|
||||
color = None
|
||||
|
||||
# 행 전체에 배경색 적용
|
||||
if color:
|
||||
for col in range(6): # 열의 개수에 따라 반복
|
||||
top_item.setBackground(col, color)
|
||||
|
||||
self.category_tree.addTopLevelItem(top_item)
|
||||
|
||||
|
|
@ -489,7 +783,7 @@ class CustomSpinBox(QSpinBox):
|
|||
|
||||
# 폰트 설정
|
||||
font = QFont()
|
||||
font.setPointSize(12) # 폰트 크기 설정
|
||||
font.setPointSize(10) # 폰트 크기 설정
|
||||
font.setBold(True) # 폰트 굵게 설정
|
||||
self.setFont(font)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
BIN
src/initialDB.db
BIN
src/initialDB.db
Binary file not shown.
BIN
src/userDB.db
BIN
src/userDB.db
Binary file not shown.
Binary file not shown.
|
|
@ -11,7 +11,8 @@ class CategoryHandler:
|
|||
async def handle_category_action(self):
|
||||
# #productMainContentContainerId 내부에서 클래스 이름 "ant-select ant-select-outlined css-1li46mu ant-select-single ant-select-show-arrow"를 포함한 요소 중 두 번째 요소 찾기
|
||||
print("[DEBUG] handle_category_action: Locating category container element...")
|
||||
category_locator = "div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=1"
|
||||
# category_locator = "div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=0"
|
||||
category_locator = "#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(1)"
|
||||
|
||||
try:
|
||||
await self.page.wait_for_selector(category_locator, timeout=5000)
|
||||
|
|
|
|||
75
title.py
75
title.py
|
|
@ -23,7 +23,6 @@ class TitleHandler:
|
|||
self.category_main_selector_with_cp = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_cp')
|
||||
self.category_main_selector_with_ss = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_ss')
|
||||
self.category_main_selector_with_esm = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_esm')
|
||||
self.certified_text_locator = self.locator_manager.get_locator('TitleLocators', 'certified_text_locator')
|
||||
self.category_text_locator = self.locator_manager.get_locator('TitleLocators', 'category_text_locator')
|
||||
self.category_text_locator_certified = self.locator_manager.get_locator('TitleLocators', 'category_text_locator_certified')
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ class TitleHandler:
|
|||
except Exception as e:
|
||||
self.logger.error(f"카테고리 추천받기 버튼 클릭 중 오류 발생: {e}", exc_info=True)
|
||||
|
||||
async def get_category(self, market='ss') -> str:
|
||||
async def get_category_ori(self, market='ss') -> str:
|
||||
"""
|
||||
카테고리를 가져오는 메서드로 인증 필요 여부에 따라 카테고리 선택자를 다르게 처리합니다.
|
||||
|
||||
|
|
@ -138,27 +137,27 @@ class TitleHandler:
|
|||
try:
|
||||
self.logger.debug(f"마켓 : {market} - 카테고리 텍스트를 가져오는 중입니다.")
|
||||
if market == 'ss':
|
||||
main_category_element = await self.page.query_selector(self.category_main_selector_with_ss)
|
||||
main_category_element = self.page.locator(self.category_main_selector_with_ss)
|
||||
self.logger.debug(f"선택 마켓 : 스마트스토어 , selector : {self.category_main_selector_with_ss}, element : {main_category_element}")
|
||||
elif market == 'cp':
|
||||
main_category_element = await self.page.query_selector(self.category_main_selector_with_cp)
|
||||
main_category_element = self.page.locator(self.category_main_selector_with_cp)
|
||||
self.logger.debug(f"선택 마켓 : 쿠팡 , selector : {self.category_main_selector_with_cp}, element : {main_category_element}")
|
||||
elif market == 'esm':
|
||||
main_category_element = await self.page.query_selector(self.category_main_selector_with_esm)
|
||||
main_category_element = self.page.locator(self.category_main_selector_with_esm)
|
||||
self.logger.debug(f"선택 마켓 : ESM , selector : {self.category_main_selector_with_esm}, element : {main_category_element}")
|
||||
if not main_category_element:
|
||||
self.logger.error("카테고리 메인 선택자를 찾을 수 없습니다.")
|
||||
return ""
|
||||
|
||||
certified_text_element = await main_category_element.query_selector(self.certified_text_locator)
|
||||
certified_text_element = main_category_element.locator(self.certified_text_locator)
|
||||
if certified_text_element:
|
||||
certified_text = await certified_text_element.inner_text()
|
||||
certified_text = certified_text_element.inner_text()
|
||||
if "인증" in certified_text:
|
||||
category_text_element = await main_category_element.query_selector(self.category_text_locator_certified)
|
||||
category_text_element = main_category_element.locator(self.category_text_locator_certified)
|
||||
self.logger.debug(f"카테고리 인증 필요 발생: {category_text}")
|
||||
else:
|
||||
category_text_element = certified_text_element
|
||||
category_text = await category_text_element.inner_text() if category_text_element else ""
|
||||
category_text = category_text_element.inner_text() if category_text_element else ""
|
||||
self.logger.debug(f"카테고리 텍스트: {category_text}")
|
||||
return category_text
|
||||
else:
|
||||
|
|
@ -166,4 +165,60 @@ class TitleHandler:
|
|||
return ""
|
||||
except Exception as e:
|
||||
self.logger.error(f"카테고리 텍스트 가져오기 중 오류 발생: {e}", exc_info=True)
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
async def get_category(self, market='ss') -> str:
|
||||
"""
|
||||
카테고리를 가져오는 메서드로 인증 필요 여부에 따라 카테고리 선택자를 다르게 처리합니다.
|
||||
|
||||
Returns:
|
||||
str: 카테고리 텍스트
|
||||
"""
|
||||
try:
|
||||
self.logger.debug(f"마켓 : {market} - 카테고리 텍스트를 가져오는 중입니다.")
|
||||
|
||||
if market == 'ss':
|
||||
category_locator = self.category_main_selector_with_ss
|
||||
elif market == 'cp':
|
||||
category_locator = self.category_main_selector_with_cp
|
||||
elif market == 'esm':
|
||||
category_locator = self.category_main_selector_with_esm
|
||||
|
||||
self.logger.debug(f"category_locator : {category_locator}")
|
||||
|
||||
await self.page.wait_for_selector(category_locator, timeout=5000, state="attached") # 요소가 나타날 때까지 대기
|
||||
main_category_element = self.page.locator(category_locator) # 대기 후 동기적으로 요소 가져오기
|
||||
self.logger.debug(f"main_category_element : {main_category_element}")
|
||||
|
||||
if not await main_category_element.count():
|
||||
self.logger.error("카테고리 메인 선택자를 찾을 수 없습니다.")
|
||||
return ""
|
||||
|
||||
# 인증 텍스트 요소 선택
|
||||
category_text_element = main_category_element.locator(self.category_text_locator)
|
||||
|
||||
self.logger.debug(f"category_text_element : {category_text_element}")
|
||||
|
||||
if await category_text_element.count():
|
||||
category_text = await category_text_element.inner_text()
|
||||
|
||||
if "인증" in category_text:
|
||||
self.logger.debug(f"카테고리 인증 필요 발생 category_text = {category_text}")
|
||||
category_text_certified_element = main_category_element.locator(self.category_text_locator_certified)
|
||||
|
||||
if await category_text_certified_element.count():
|
||||
category_text = await category_text_certified_element.inner_text()
|
||||
self.logger.debug(f"인증 필요 카테고리 text = {category_text}")
|
||||
else:
|
||||
self.logger.debug(f"카테고리 text = {category_text}")
|
||||
|
||||
return category_text
|
||||
|
||||
else:
|
||||
self.logger.error("카테고리 인증 요소를 찾을 수 없습니다.")
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"카테고리 텍스트 가져오기 중 오류 발생: {e}", exc_info=True)
|
||||
return ""
|
||||
|
|
|
|||
Loading…
Reference in New Issue