qrpa 1.0.42__tar.gz → 1.0.43__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of qrpa might be problematic. Click here for more details.

Files changed (33) hide show
  1. {qrpa-1.0.42 → qrpa-1.0.43}/PKG-INFO +1 -1
  2. {qrpa-1.0.42 → qrpa-1.0.43}/pyproject.toml +1 -1
  3. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/fun_excel.py +43 -22
  4. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/shein_excel.py +2 -1
  5. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/shein_lib.py +27 -3
  6. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/shein_ziniao.py +43 -46
  7. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa.egg-info/PKG-INFO +1 -1
  8. {qrpa-1.0.42 → qrpa-1.0.43}/README.md +0 -0
  9. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/RateLimitedSender.py +0 -0
  10. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/__init__.py +0 -0
  11. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/db_migrator.py +0 -0
  12. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/feishu_bot_app.py +0 -0
  13. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/feishu_client.py +0 -0
  14. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/feishu_logic.py +0 -0
  15. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/fun_base.py +0 -0
  16. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/fun_file.py +0 -0
  17. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/fun_web.py +0 -0
  18. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/fun_win.py +0 -0
  19. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/shein_daily_report_model.py +0 -0
  20. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/shein_sqlite.py +0 -0
  21. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/temu_chrome.py +0 -0
  22. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/temu_excel.py +0 -0
  23. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/temu_lib.py +0 -0
  24. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/time_utils.py +0 -0
  25. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/time_utils_example.py +0 -0
  26. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa/wxwork.py +0 -0
  27. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa.egg-info/SOURCES.txt +0 -0
  28. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa.egg-info/dependency_links.txt +0 -0
  29. {qrpa-1.0.42 → qrpa-1.0.43}/qrpa.egg-info/top_level.txt +0 -0
  30. {qrpa-1.0.42 → qrpa-1.0.43}/setup.cfg +0 -0
  31. {qrpa-1.0.42 → qrpa-1.0.43}/setup.py +0 -0
  32. {qrpa-1.0.42 → qrpa-1.0.43}/tests/test_db_migrator.py +0 -0
  33. {qrpa-1.0.42 → qrpa-1.0.43}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.42
3
+ Version: 1.0.43
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qrpa"
7
- version = "1.0.42"
7
+ version = "1.0.43"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -149,37 +149,58 @@ def merge_by_column_v2(sheet, column_name, other_columns):
149
149
  log(f'未找到合并的列名: {column_name}')
150
150
  return
151
151
 
152
- data = sheet.range(f'{col_letter}1').expand('table').value
153
- # col_index = column_name_to_index(col_letter)
154
- start_row = 1
155
- merge_ranges = [] # 用来存储所有待合并的单元格范围
152
+ # 更安全的数据获取方式,确保获取完整的数据范围
153
+ last_row = get_last_row(sheet, col_letter)
154
+ data = sheet.range(f'{col_letter}1:{col_letter}{last_row}').value
155
+
156
+ # 确保data是列表格式
157
+ if not isinstance(data, list):
158
+ data = [data]
159
+
160
+ log(f'数据范围: {col_letter}1:{col_letter}{last_row}, 数据长度: {len(data)}')
161
+
162
+ start_row = 2 # 从第2行开始,跳过表头
163
+ merge_row_ranges = [] # 用来存储需要合并的行范围 (start_row, end_row)
156
164
 
157
- # 缓存其他列的列号
158
- other_columns_index = {}
165
+ # 获取所有需要合并的列
166
+ all_columns = [col_letter] # 主列
159
167
  for col in other_columns:
160
168
  col_name = find_column_by_data(sheet, 1, col)
161
169
  if col_name:
162
- other_columns_index[col_name] = column_name_to_index(col_name)
170
+ all_columns.append(col_name)
171
+
172
+ log(f'需要合并的列: {all_columns}')
163
173
 
174
+ # 遍历数据行,从第2行开始(索引1)到最后一行
164
175
  for row in range(2, len(data) + 1):
165
- log(f'查找 {row}/{len(data)}')
166
- if data[row - 1][0] != data[row - 2][0]:
167
- if row - start_row > 1:
168
- # 将合并范围加入列表
169
- merge_ranges.append((col_letter, start_row, row - 1))
170
- for col_name, col_index in other_columns_index.items():
171
- merge_ranges.append((col_name, start_row, row - 1))
176
+ log(f'查找 {row}/{len(data)}, 当前值: {data[row-1] if row-1 < len(data) else "超出范围"}, 前一个值: {data[row-2] if row-2 < len(data) else "超出范围"}')
177
+
178
+ # 检查值是否发生变化(不是最后一行时)
179
+ value_changed = row < len(data) and data[row - 1] != data[row - 2]
180
+
181
+ if value_changed:
182
+ # 值发生变化,处理前一组
183
+ if row - start_row > 0:
184
+ end_row = row - 1
185
+ log(f'添加合并范围: {start_row} 到 {end_row}')
186
+ merge_row_ranges.append((start_row, end_row))
172
187
  start_row = row
173
188
 
174
- if len(data) - start_row > 1:
175
- merge_ranges.append((col_letter, start_row, len(data)))
176
- for col_name, col_index in other_columns_index.items():
177
- merge_ranges.append((col_name, start_row, len(data)))
189
+ # 处理最后一组数据(循环结束后,start_row 到数据末尾)
190
+ if start_row <= len(data):
191
+ log(f'处理最后一组: {start_row} {len(data)}')
192
+ merge_row_ranges.append((start_row, len(data)))
178
193
 
179
- # 批量合并单元格
180
- for col_name, start, end in merge_ranges:
181
- log(f'处理 {col_name}{start}:{col_name}{end} merge')
182
- sheet.range(f'{col_name}{start}:{col_name}{end}').merge()
194
+ log(f'行合并范围: {merge_row_ranges}')
195
+
196
+ # 对每个行范围,在所有指定列中执行合并
197
+ for start_row, end_row in merge_row_ranges:
198
+ if start_row < end_row: # 只有当开始行小于结束行时才合并
199
+ for col_name in all_columns:
200
+ log(f'处理 {col_name}{start_row}:{col_name}{end_row} merge')
201
+ sheet.range(f'{col_name}{start_row}:{col_name}{end_row}').merge()
202
+ else:
203
+ log(f'跳过无效合并范围: {start_row} 到 {end_row}')
183
204
 
184
205
  def merge_by_column(sheet, column_name, other_columns):
185
206
  log('正在处理合并单元格')
@@ -209,7 +209,8 @@ class SheinExcel:
209
209
  ])
210
210
 
211
211
  def format_return_list(self, sheet):
212
- merge_by_column_v2(sheet, '退货单号', ['签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '包裹名', '包裹号', '退货计划单号', '订单号', '发货单', '退货出库时间', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址'])
212
+ merge_by_column_v2(sheet, '包裹号', ['包裹名'])
213
+ merge_by_column_v2(sheet, '退货单号', ['签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '退货计划单号', '订单号', '发货单', '退货出库时间', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址'])
213
214
  beautify_title(sheet)
214
215
  add_borders(sheet)
215
216
  format_to_datetime(sheet, ['时间'])
@@ -135,6 +135,21 @@ class SheinLib:
135
135
  log(qc_report_url)
136
136
  return qc_report_url
137
137
 
138
+ # 获取稽查报表
139
+ def get_inspect_report_url(self, returnOrderId):
140
+ log(f'获取稽查报告:{returnOrderId}')
141
+ url = f"https://sso.geiwohuo.com/pfmp/returnOrder/queryInspectReport"
142
+ payload = {
143
+ "returnOrderId": returnOrderId,
144
+ }
145
+ response_text = fetch(self.web_page, url, payload)
146
+ error_code = response_text.get('code')
147
+ if str(error_code) != '0':
148
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
149
+ report_url = (response_text.get('info', {}).get('data') or [{'reportUrl': '稽查报告生成中,请稍后查看'}])[0].get('reportUrl')
150
+ log(report_url)
151
+ return report_url
152
+
138
153
  def get_return_order_box_detail(self, returnOrderId):
139
154
  log(f'获取退货包裹详情: {returnOrderId}')
140
155
  url = f"https://sso.geiwohuo.com/pfmp/returnOrder/getReturnOrderBoxDetail"
@@ -151,6 +166,7 @@ class SheinLib:
151
166
 
152
167
  cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
153
168
  write_dict_to_file(cache_file, list_item)
169
+ return list_item
154
170
 
155
171
  def get_return_order_list(self, start_date, end_date, only_yesterday=1):
156
172
 
@@ -188,13 +204,21 @@ class SheinLib:
188
204
  for item in list_item:
189
205
  has_valid_package = item.get('hasPackage') == 1
190
206
  is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
191
- if has_valid_package:
207
+ returnOrderId = item['id']
208
+ return_box_detail = self.get_return_order_box_detail(returnOrderId)
209
+ item['qc_report_url'] = ''
210
+ item['report_url'] = ''
211
+ if has_valid_package and len(return_box_detail) > 0:
212
+
192
213
  if int(item['returnScrapType']) == 1:
193
214
  purchaseCode = item['sellerOrderNo']
194
215
  delivery_code = item['sellerDeliveryNo']
195
216
  item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
196
- returnOrderId = item['id']
197
- self.get_return_order_box_detail(returnOrderId)
217
+
218
+ if int(item['returnScrapType']) == 2:
219
+ item['report_url'] = self.get_inspect_report_url(returnOrderId)
220
+
221
+ item['return_box_detail'] = return_box_detail
198
222
 
199
223
  all_list_item.append(item)
200
224
  if is_valid_yesterday:
@@ -24,7 +24,6 @@ from .fun_win import find_software_install_path
24
24
  from .fun_base import log, hostname, send_exception
25
25
  from .fun_file import check_progress_json_ex, get_progress_json_ex, done_progress_json_ex, write_dict_to_file_ex
26
26
 
27
-
28
27
  class ZiniaoClient:
29
28
  """紫鸟客户端管理类"""
30
29
 
@@ -53,7 +52,7 @@ class ZiniaoClient:
53
52
  def _get_user_info(self) -> Dict[str, str]:
54
53
  """获取用户登录信息"""
55
54
  return {
56
- "company": self.config.ziniao.company,
55
+ "company" : self.config.ziniao.company,
57
56
  "username": self.config.ziniao.username,
58
57
  "password": self.config.ziniao.password
59
58
  }
@@ -90,7 +89,7 @@ class ZiniaoClient:
90
89
  def update_core(self):
91
90
  """下载所有内核,打开店铺前调用,需客户端版本5.285.7以上"""
92
91
  data = {
93
- "action": "updateCore",
92
+ "action" : "updateCore",
94
93
  "requestId": str(uuid.uuid4()),
95
94
  }
96
95
  data.update(self.user_info)
@@ -146,7 +145,6 @@ class ZiniaoClient:
146
145
  print('@@ get_exit...' + json.dumps(data))
147
146
  self.send_http(data)
148
147
 
149
-
150
148
  class ZiniaoBrowser:
151
149
  """紫鸟浏览器操作类"""
152
150
 
@@ -155,22 +153,22 @@ class ZiniaoBrowser:
155
153
  self.config = config
156
154
 
157
155
  def open_store(self, store_info: str, isWebDriverReadOnlyMode: int = 0,
158
- isprivacy: int = 0, isHeadless: int = 0,
159
- cookieTypeSave: int = 0, jsInfo: str = "") -> Dict[str, Any]:
156
+ isprivacy: int = 0, isHeadless: int = 0,
157
+ cookieTypeSave: int = 0, jsInfo: str = "") -> Dict[str, Any]:
160
158
  """打开店铺"""
161
159
  request_id = str(uuid.uuid4())
162
160
  data = {
163
- "action": "startBrowser",
164
- "isWaitPluginUpdate": 0,
165
- "isHeadless": isHeadless,
166
- "requestId": request_id,
161
+ "action" : "startBrowser",
162
+ "isWaitPluginUpdate" : 0,
163
+ "isHeadless" : isHeadless,
164
+ "requestId" : request_id,
167
165
  "isWebDriverReadOnlyMode": isWebDriverReadOnlyMode,
168
- "cookieTypeLoad": 0,
169
- "cookieTypeSave": cookieTypeSave,
170
- "runMode": "1",
171
- "isLoadUserPlugin": False,
172
- "pluginIdType": 1,
173
- "privacyMode": isprivacy
166
+ "cookieTypeLoad" : 0,
167
+ "cookieTypeSave" : cookieTypeSave,
168
+ "runMode" : "1",
169
+ "isLoadUserPlugin" : False,
170
+ "pluginIdType" : 1,
171
+ "privacyMode" : isprivacy
174
172
  }
175
173
  data.update(self.client.user_info)
176
174
 
@@ -196,9 +194,9 @@ class ZiniaoBrowser:
196
194
  """关闭店铺"""
197
195
  request_id = str(uuid.uuid4())
198
196
  data = {
199
- "action": "stopBrowser",
200
- "requestId": request_id,
201
- "duplicate": 0,
197
+ "action" : "stopBrowser",
198
+ "requestId" : request_id,
199
+ "duplicate" : 0,
202
200
  "browserOauth": browser_oauth
203
201
  }
204
202
  data.update(self.client.user_info)
@@ -213,11 +211,11 @@ class ZiniaoBrowser:
213
211
  print(f"Fail {json.dumps(r, ensure_ascii=False)} ")
214
212
  raise RuntimeError("关闭店铺失败")
215
213
 
216
- def get_browser_list(self) -> List[Dict[str, Any]]:
214
+ def get_browser_list(self, platform_name="SHEIN-全球") -> List[Dict[str, Any]]:
217
215
  """获取浏览器列表"""
218
216
  request_id = str(uuid.uuid4())
219
217
  data = {
220
- "action": "getBrowserList",
218
+ "action" : "getBrowserList",
221
219
  "requestId": request_id
222
220
  }
223
221
  data.update(self.client.user_info)
@@ -225,7 +223,8 @@ class ZiniaoBrowser:
225
223
  r = self.client.send_http(data)
226
224
  if str(r.get("statusCode")) == "0":
227
225
  print(r)
228
- return r.get("browserList", [])
226
+ # return r.get("browserList", [])
227
+ return [site for site in r.get("browserList", []) if site.get("platform_name") == platform_name]
229
228
  elif str(r.get("statusCode")) == "-10003":
230
229
  print(f"login Err {json.dumps(r, ensure_ascii=False)}")
231
230
  raise RuntimeError("登录错误")
@@ -267,7 +266,6 @@ class ZiniaoBrowser:
267
266
  # 标记完成
268
267
  done_progress_json_ex(self.config, task_key, store_name)
269
268
 
270
-
271
269
  class ZiniaoTaskManager:
272
270
  """紫鸟任务管理类"""
273
271
 
@@ -276,10 +274,10 @@ class ZiniaoTaskManager:
276
274
  self.config = config
277
275
 
278
276
  def run_single_store_task(self, browser_info: Dict[str, Any],
279
- run_func: Callable, task_key: str,
280
- just_store_username: Optional[List[str]] = None,
281
- is_skip_store: Optional[Callable] = None
282
- ):
277
+ run_func: Callable, task_key: str,
278
+ just_store_username: Optional[List[str]] = None,
279
+ is_skip_store: Optional[Callable] = None
280
+ ):
283
281
  """运行单个店铺的任务"""
284
282
  retry_count = 0
285
283
  while True:
@@ -350,20 +348,20 @@ class ZiniaoTaskManager:
350
348
  break
351
349
 
352
350
  def run_all_stores_task(self, browser_list: List[Dict[str, Any]],
353
- run_func: Callable, task_key: str,
354
- just_store_username: Optional[List[str]] = None,
355
- is_skip_store: Optional[Callable] = None
356
- ):
351
+ run_func: Callable, task_key: str,
352
+ just_store_username: Optional[List[str]] = None,
353
+ is_skip_store: Optional[Callable] = None
354
+ ):
357
355
  """循环运行所有店铺的任务"""
358
356
  for browser_info in browser_list:
359
357
  self.run_single_store_task(browser_info, run_func, task_key, just_store_username, is_skip_store)
360
358
 
361
359
  def run_with_thread_pool(self, browser_list: List[Dict[str, Any]],
362
- max_threads: int = 3, run_func: Callable = None,
363
- task_key: str = None,
364
- just_store_username: Optional[List[str]] = None,
365
- is_skip_store: Optional[Callable] = None
366
- ):
360
+ max_threads: int = 3, run_func: Callable = None,
361
+ task_key: str = None,
362
+ just_store_username: Optional[List[str]] = None,
363
+ is_skip_store: Optional[Callable] = None
364
+ ):
367
365
  """使用线程池控制最大并发线程数运行任务"""
368
366
  with ThreadPoolExecutor(max_workers=max_threads) as executor:
369
367
  task = partial(self.run_single_store_task,
@@ -373,7 +371,6 @@ class ZiniaoTaskManager:
373
371
  log(f'店铺总数: {len(browser_list)}')
374
372
  executor.map(task, browser_list)
375
373
 
376
-
377
374
  class ZiniaoRunner:
378
375
  """紫鸟主运行器类"""
379
376
 
@@ -387,13 +384,14 @@ class ZiniaoRunner:
387
384
  self.task_manager = ZiniaoTaskManager(self.browser, config)
388
385
 
389
386
  def execute(self, run_prepare: Optional[Callable] = None,
390
- run: Optional[Callable] = None,
391
- run_summary: Optional[Callable] = None,
392
- run_notify: Optional[Callable] = None,
393
- task_key: Optional[str] = None,
394
- just_store_username: Optional[List[str]] = None,
395
- is_skip_store: Optional[Callable] = None,
396
- ):
387
+ run: Optional[Callable] = None,
388
+ run_summary: Optional[Callable] = None,
389
+ run_notify: Optional[Callable] = None,
390
+ task_key: Optional[str] = None,
391
+ just_store_username: Optional[List[str]] = None,
392
+ is_skip_store: Optional[Callable] = None,
393
+ platform_name: Optional[str] = "SHEIN-全球",
394
+ ):
397
395
  """主执行入口"""
398
396
  # 前置执行 if run_prepare:
399
397
  run_prepare()
@@ -408,7 +406,7 @@ class ZiniaoRunner:
408
406
 
409
407
  # 获取店铺列表
410
408
  print("=====获取店铺列表=====")
411
- browser_list = self.browser.get_browser_list()
409
+ browser_list = self.browser.get_browser_list(platform_name=platform_name)
412
410
  if not browser_list:
413
411
  print("browser list is empty")
414
412
  raise RuntimeError("店铺列表为空")
@@ -445,6 +443,5 @@ class ZiniaoRunner:
445
443
  # 关闭客户端
446
444
  self.client.exit()
447
445
 
448
-
449
446
  if __name__ == "__main__":
450
447
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.42
3
+ Version: 1.0.43
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes