qrpa 1.1.34__tar.gz → 1.1.36__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 (40) hide show
  1. {qrpa-1.1.34 → qrpa-1.1.36}/PKG-INFO +1 -1
  2. {qrpa-1.1.34 → qrpa-1.1.36}/pyproject.toml +1 -1
  3. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/mysql_module/new_product_analysis_model.py +67 -2
  4. qrpa-1.1.36/qrpa/mysql_module/shein_store_model.py +594 -0
  5. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_lib.py +253 -2
  6. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa.egg-info/PKG-INFO +1 -1
  7. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa.egg-info/SOURCES.txt +1 -0
  8. {qrpa-1.1.34 → qrpa-1.1.36}/README.md +0 -0
  9. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/RateLimitedSender.py +0 -0
  10. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/__init__.py +0 -0
  11. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/db_migrator.py +0 -0
  12. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/feishu_bot_app.py +0 -0
  13. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/feishu_client.py +0 -0
  14. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/feishu_logic.py +0 -0
  15. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/fun_base.py +0 -0
  16. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/fun_excel.py +0 -0
  17. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/fun_file.py +0 -0
  18. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/fun_web.py +0 -0
  19. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/fun_win.py +0 -0
  20. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/mysql_module/__init__.py +0 -0
  21. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/mysql_module/shein_ledger_model.py +0 -0
  22. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/mysql_module/shein_product_model.py +0 -0
  23. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/mysql_module/shein_return_order_model.py +0 -0
  24. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_daily_report_model.py +0 -0
  25. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_excel.py +0 -0
  26. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_mysql.py +0 -0
  27. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_sqlite.py +0 -0
  28. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/shein_ziniao.py +0 -0
  29. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/temu_chrome.py +0 -0
  30. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/temu_excel.py +0 -0
  31. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/temu_lib.py +0 -0
  32. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/time_utils.py +0 -0
  33. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/time_utils_example.py +0 -0
  34. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa/wxwork.py +0 -0
  35. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa.egg-info/dependency_links.txt +0 -0
  36. {qrpa-1.1.34 → qrpa-1.1.36}/qrpa.egg-info/top_level.txt +0 -0
  37. {qrpa-1.1.34 → qrpa-1.1.36}/setup.cfg +0 -0
  38. {qrpa-1.1.34 → qrpa-1.1.36}/setup.py +0 -0
  39. {qrpa-1.1.34 → qrpa-1.1.36}/tests/test_db_migrator.py +0 -0
  40. {qrpa-1.1.34 → qrpa-1.1.36}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.1.34
3
+ Version: 1.1.36
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.1.34"
7
+ version = "1.1.36"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -63,6 +63,7 @@ class SheinNewProductAnalysis(Base):
63
63
  ab_test = Column(Text, nullable=True, comment='AB测试数据(JSON)')
64
64
 
65
65
  # 分析字段(不参与更新)
66
+ front_price = Column(DECIMAL(10, 2), nullable=True, comment='前台价格')
66
67
  reason_analysis = Column(Text, nullable=True, comment='原因分析')
67
68
  optimization_action = Column(Text, nullable=True, comment='优化动作')
68
69
 
@@ -144,7 +145,7 @@ class NewProductAnalysisManager:
144
145
 
145
146
  if existing:
146
147
  # 更新现有记录(排除不更新的分析字段)
147
- exclude_fields = {'reason_analysis', 'optimization_action'}
148
+ exclude_fields = {'front_price', 'reason_analysis', 'optimization_action'}
148
149
  for key, value in data.items():
149
150
  if key not in exclude_fields:
150
151
  setattr(existing, key, value)
@@ -235,7 +236,13 @@ class NewProductAnalysisManager:
235
236
  prom_detail = prom.get('promDetail', [])
236
237
 
237
238
  # 根据promId长度判断取哪些字段
238
- if len(str(prom_id)) > 6 and prom_detail:
239
+ if len(str(prom_id)) >= 11 and prom_detail:
240
+ # 营销工具:取第一个detail的数据
241
+ detail = prom_detail if prom_detail else {}
242
+ prom_info['attend_num_sum'] = detail.get('act_stock_num')
243
+ prom_info['supply_price'] = detail.get('sku_price')
244
+ prom_info['product_act_price'] = detail.get('act_sku_price')
245
+ elif len(str(prom_id)) >= 8 and prom_detail:
239
246
  # 营销工具:取第一个detail的数据
240
247
  detail = prom_detail[0] if prom_detail else {}
241
248
  prom_info['attend_num_sum'] = detail.get('attend_num_sum')
@@ -328,6 +335,41 @@ class NewProductAnalysisManager:
328
335
  # 调用insert_data方法插入数据
329
336
  return self.insert_data(data_list)
330
337
 
338
+ def update_front_price(self, stat_date, skc, front_price):
339
+ """
340
+ 更新指定SKC的前台价格
341
+
342
+ Args:
343
+ stat_date (str): 统计日期
344
+ skc (str): 平台SKC
345
+ front_price (float): 前台价格
346
+
347
+ Returns:
348
+ bool: 是否成功
349
+ """
350
+ session = self.Session()
351
+ try:
352
+ record = session.query(SheinNewProductAnalysis).filter(
353
+ SheinNewProductAnalysis.stat_date == stat_date,
354
+ SheinNewProductAnalysis.skc == skc
355
+ ).first()
356
+
357
+ if record:
358
+ record.front_price = front_price
359
+ setattr(record, 'updated_at', datetime.now())
360
+ session.commit()
361
+ print(f"成功更新前台价格: {skc} ({stat_date}) -> {front_price}")
362
+ return True
363
+ else:
364
+ print(f"未找到记录: SKC={skc}, 日期={stat_date}")
365
+ return False
366
+ except Exception as e:
367
+ session.rollback()
368
+ print(f"更新前台价格失败: {e}")
369
+ raise
370
+ finally:
371
+ session.close()
372
+
331
373
  def delete_records_by_date(self, store_username, stat_date):
332
374
  """
333
375
  删除指定日期的记录
@@ -410,6 +452,23 @@ def get_records_by_skc(store_username, skc):
410
452
  manager = get_new_product_analysis_manager()
411
453
  return manager.get_records_by_skc(store_username, skc)
412
454
 
455
+
456
+ def update_front_price(stat_date, skc, front_price):
457
+ """
458
+ 更新前台价格的便捷函数
459
+
460
+ Args:
461
+ stat_date (str): 统计日期
462
+ skc (str): 平台SKC
463
+ front_price (float): 前台价格
464
+
465
+ Returns:
466
+ bool: 是否成功
467
+ """
468
+ manager = get_new_product_analysis_manager()
469
+ return manager.update_front_price(stat_date, skc, front_price)
470
+
471
+
413
472
  if __name__ == '__main__':
414
473
  # 测试代码
415
474
  manager = NewProductAnalysisManager()
@@ -427,3 +486,9 @@ if __name__ == '__main__':
427
486
  json_data = json.load(f)
428
487
  count = manager.import_from_json(json_data)
429
488
  print(f"成功导入 {count} 条记录")
489
+
490
+ # 更新前台价格(手动设置,后续导入不会覆盖)
491
+ # manager.update_front_price('2025-10-15', 'si2409238815318914', 19.99)
492
+
493
+ # 或使用便捷函数
494
+ # update_front_price('2025-10-15', 'si2409238815318914', 19.99)
@@ -0,0 +1,594 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 店铺信息数据模型
5
+ 使用SQLAlchemy定义shein_store表结构
6
+ """
7
+
8
+ from sqlalchemy import create_engine, Column, Integer, String, BigInteger, DateTime, Index
9
+ from sqlalchemy.ext.declarative import declarative_base
10
+ from sqlalchemy.orm import sessionmaker
11
+ from datetime import datetime
12
+ import json
13
+
14
+ # 创建基类
15
+ Base = declarative_base()
16
+
17
+ class SheinStore(Base):
18
+ """
19
+ 希音店铺信息表
20
+ 存储店铺基础信息
21
+ """
22
+ __tablename__ = 'shein_store'
23
+
24
+ # 主键ID
25
+ id = Column(BigInteger, primary_key=True, autoincrement=True, comment='主键ID')
26
+
27
+ # 店铺账号信息(唯一)
28
+ user_name = Column(String(100), nullable=False, unique=True, comment='店铺账号')
29
+ store_username = Column(String(100), nullable=True, comment='店铺用户名')
30
+ store_name = Column(String(200), nullable=True, comment='店铺名称')
31
+
32
+ # 用户ID信息
33
+ user_id = Column(BigInteger, nullable=True, comment='用户ID')
34
+ supplier_id = Column(BigInteger, nullable=True, comment='供应商ID')
35
+ main_user_name = Column(String(100), nullable=True, comment='主账号用户名')
36
+ main_user_id = Column(BigInteger, nullable=True, comment='主账号用户ID')
37
+
38
+ # ULP信息
39
+ ulp_name = Column(String(200), nullable=True, comment='ULP名称')
40
+ ulp_en_name = Column(String(200), nullable=True, comment='ULP英文名称')
41
+ ulp_emplid = Column(String(100), nullable=True, comment='ULP员工ID')
42
+ is_ulp_login = Column(Integer, nullable=True, comment='是否ULP登录')
43
+
44
+ # 时区信息
45
+ timezone = Column(String(100), nullable=True, comment='时区')
46
+ timezone_name = Column(String(200), nullable=True, comment='时区名称')
47
+ utc_timezone = Column(String(50), nullable=True, comment='UTC时区')
48
+ area_timezone = Column(String(100), nullable=True, comment='地区时区')
49
+
50
+ # 系统配置
51
+ switch_new_menu = Column(Integer, nullable=True, comment='是否切换新菜单')
52
+ supplier_user_name = Column(String(100), nullable=True, comment='供应商用户名')
53
+ sso_top_nav = Column(Integer, nullable=True, comment='SSO顶部导航')
54
+ sso_host = Column(String(500), nullable=True, comment='SSO主机地址')
55
+
56
+ # 类目信息
57
+ category_id = Column(BigInteger, nullable=True, comment='类目ID')
58
+ category_out_id = Column(BigInteger, nullable=True, comment='外部类目ID')
59
+ lv1_category_id = Column(BigInteger, nullable=True, comment='一级类目ID')
60
+ lv1_category_name = Column(String(200), nullable=True, comment='一级类目名称')
61
+ lv2_category_name = Column(String(200), nullable=True, comment='二级类目名称')
62
+
63
+ # 其他信息
64
+ supplier_source = Column(Integer, nullable=True, comment='供应商来源')
65
+ external_id = Column(BigInteger, nullable=True, comment='外部ID')
66
+ store_code = Column(BigInteger, nullable=True, comment='店铺编码')
67
+ merchant_code = Column(String(100), nullable=True, comment='商户编码')
68
+ schat_id = Column(BigInteger, nullable=True, comment='Schat ID')
69
+
70
+ # 管理字段(不参与更新)
71
+ store_manager_id = Column(BigInteger, default=0, comment='店长ID(用户表ID)')
72
+ is_deleted = Column(Integer, nullable=True, default=0, comment='软删除标志(0-未删除,1-已删除)')
73
+
74
+ # 备注
75
+ remark = Column(String(500), nullable=True, comment='备注')
76
+
77
+ # 时间戳
78
+ created_at = Column(DateTime, nullable=True, default=datetime.now, comment='创建时间')
79
+ updated_at = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now, comment='更新时间')
80
+
81
+ # 创建索引
82
+ __table_args__ = (
83
+ Index('idx_store_username', 'store_username',unique=True),
84
+ )
85
+
86
+ def __repr__(self):
87
+ return f"<SheinStore(id={self.id}, user_name='{self.user_name}', store_name='{self.store_name}')>"
88
+
89
+
90
+ class SheinStoreManager:
91
+ """
92
+ 店铺信息数据管理器
93
+ 提供数据库操作相关方法
94
+ """
95
+
96
+ @classmethod
97
+ def __init__(self, database_url):
98
+ """
99
+ 初始化数据库连接
100
+
101
+ Args:
102
+ database_url (str): 数据库连接URL
103
+ """
104
+ print(f"连接数据库: {database_url}")
105
+ self.engine = create_engine(database_url, echo=False)
106
+ self.Session = sessionmaker(bind=self.engine)
107
+
108
+ def create_table(self):
109
+ """
110
+ 创建数据表
111
+ """
112
+ Base.metadata.create_all(self.engine)
113
+ print("店铺表创建成功")
114
+
115
+ def insert_data(self, data_list):
116
+ """
117
+ 批量插入数据
118
+
119
+ Args:
120
+ data_list (list): 数据字典列表
121
+
122
+ Returns:
123
+ int: 成功插入的记录数
124
+ """
125
+ session = self.Session()
126
+ try:
127
+ insert_count = 0
128
+ update_count = 0
129
+
130
+ for data in data_list:
131
+ # 检查是否存在(根据唯一索引:user_name)
132
+ existing = session.query(SheinStore).filter(
133
+ SheinStore.user_name == data.get('user_name')
134
+ ).first()
135
+
136
+ if existing:
137
+ # 更新现有记录(排除不更新的字段)
138
+ exclude_fields = {'store_manager_id', 'is_deleted'}
139
+ for key, value in data.items():
140
+ if key not in exclude_fields:
141
+ setattr(existing, key, value)
142
+ setattr(existing, 'updated_at', datetime.now())
143
+ update_count += 1
144
+ else:
145
+ # 插入新记录
146
+ new_record = SheinStore(**data)
147
+ session.add(new_record)
148
+ insert_count += 1
149
+
150
+ session.commit()
151
+ print(f"成功插入 {insert_count} 条记录,更新 {update_count} 条记录")
152
+ return insert_count
153
+ except Exception as e:
154
+ session.rollback()
155
+ print(f"插入数据失败: {e}")
156
+ raise
157
+ finally:
158
+ session.close()
159
+
160
+ def import_from_json_dict(self, json_dict):
161
+ """
162
+ 从JSON字典数据导入到数据库(JSON格式:{userName: {店铺信息}})
163
+
164
+ Args:
165
+ json_dict (dict): JSON字典数据,key为userName,value为店铺信息
166
+
167
+ Returns:
168
+ int: 成功导入的记录数
169
+ """
170
+ print(f"开始处理 {len(json_dict)} 个店铺数据")
171
+ data_list = []
172
+ skipped_count = 0
173
+
174
+ for user_name, item in json_dict.items():
175
+ # 验证必填字段
176
+ if not user_name:
177
+ print(f"⚠️ 警告: 缺少必填字段 user_name,跳过该记录")
178
+ skipped_count += 1
179
+ continue
180
+
181
+ # 构建数据库记录(字段名转换:驼峰转下划线)
182
+ record = {
183
+ 'user_name': item.get('userName'),
184
+ 'store_username': item.get('store_username'),
185
+ 'store_name': item.get('store_name'),
186
+ 'user_id': item.get('userId'),
187
+ 'supplier_id': item.get('supplierId'),
188
+ 'main_user_name': item.get('mainUserName'),
189
+ 'main_user_id': item.get('mainUserId'),
190
+ 'ulp_name': item.get('ulpName'),
191
+ 'ulp_en_name': item.get('ulpEnName'),
192
+ 'ulp_emplid': item.get('ulpEmplid'),
193
+ 'is_ulp_login': item.get('isUlpLogin'),
194
+ 'timezone': item.get('timezone'),
195
+ 'timezone_name': item.get('timezoneName'),
196
+ 'utc_timezone': item.get('utcTimezone'),
197
+ 'area_timezone': item.get('areaTimezone'),
198
+ 'switch_new_menu': item.get('switchNewMenu'),
199
+ 'supplier_user_name': item.get('supplierUserName'),
200
+ 'sso_top_nav': item.get('ssoTopNav'),
201
+ 'sso_host': item.get('ssoHost'),
202
+ 'category_id': item.get('categoryId'),
203
+ 'category_out_id': item.get('categoryOutId'),
204
+ 'lv1_category_id': item.get('lv1CategoryId'),
205
+ 'lv1_category_name': item.get('lv1CategoryName'),
206
+ 'lv2_category_name': item.get('lv2CategoryName'),
207
+ 'supplier_source': item.get('supplierSource'),
208
+ 'external_id': item.get('externalId'),
209
+ 'store_code': item.get('storeCode'),
210
+ 'merchant_code': item.get('merchantCode'),
211
+ 'schat_id': item.get('schatId'),
212
+ }
213
+
214
+ print(f"✓ 处理店铺: {record['user_name']} - {record['store_name']}")
215
+ data_list.append(record)
216
+
217
+ if skipped_count > 0:
218
+ print(f"⚠️ 共跳过 {skipped_count} 条记录(缺少必填字段)")
219
+
220
+ # 调用insert_data方法插入数据
221
+ return self.insert_data(data_list)
222
+
223
+ def import_from_json_list(self, json_list):
224
+ """
225
+ 从JSON列表数据导入到数据库(JSON格式:[{店铺信息}, {店铺信息}])
226
+
227
+ Args:
228
+ json_list (list): JSON列表数据
229
+
230
+ Returns:
231
+ int: 成功导入的记录数
232
+ """
233
+ # 转换为字典格式,然后调用 import_from_json_dict
234
+ json_dict = {item.get('userName'): item for item in json_list if item.get('userName')}
235
+ return self.import_from_json_dict(json_dict)
236
+
237
+ def get_store_by_username(self, user_name, include_deleted=False):
238
+ """
239
+ 根据用户名查询店铺信息
240
+
241
+ Args:
242
+ user_name (str): 店铺账号
243
+ include_deleted (bool): 是否包含已删除的店铺,默认False
244
+
245
+ Returns:
246
+ SheinStore: 店铺对象
247
+ """
248
+ session = self.Session()
249
+ try:
250
+ query = session.query(SheinStore).filter(
251
+ SheinStore.user_name == user_name
252
+ )
253
+
254
+ # 默认只查询未删除的
255
+ if not include_deleted:
256
+ query = query.filter(SheinStore.is_deleted == 0)
257
+
258
+ store = query.first()
259
+ return store
260
+ finally:
261
+ session.close()
262
+
263
+ def get_all_stores(self, include_deleted=False):
264
+ """
265
+ 获取所有店铺列表
266
+
267
+ Args:
268
+ include_deleted (bool): 是否包含已删除的店铺,默认False
269
+
270
+ Returns:
271
+ list: 店铺列表
272
+ """
273
+ session = self.Session()
274
+ try:
275
+ query = session.query(SheinStore)
276
+
277
+ # 默认只查询未删除的
278
+ if not include_deleted:
279
+ query = query.filter(SheinStore.is_deleted == 0)
280
+
281
+ stores = query.all()
282
+ return stores
283
+ finally:
284
+ session.close()
285
+
286
+ def get_stores_by_category(self, lv1_category_name=None, lv2_category_name=None, include_deleted=False):
287
+ """
288
+ 根据类目查询店铺列表
289
+
290
+ Args:
291
+ lv1_category_name (str): 一级类目名称
292
+ lv2_category_name (str): 二级类目名称
293
+ include_deleted (bool): 是否包含已删除的店铺,默认False
294
+
295
+ Returns:
296
+ list: 店铺列表
297
+ """
298
+ session = self.Session()
299
+ try:
300
+ query = session.query(SheinStore)
301
+
302
+ # 默认只查询未删除的
303
+ if not include_deleted:
304
+ query = query.filter(SheinStore.is_deleted == 0)
305
+
306
+ if lv1_category_name:
307
+ query = query.filter(SheinStore.lv1_category_name == lv1_category_name)
308
+
309
+ if lv2_category_name:
310
+ query = query.filter(SheinStore.lv2_category_name == lv2_category_name)
311
+
312
+ stores = query.all()
313
+ return stores
314
+ finally:
315
+ session.close()
316
+
317
+ def update_store_manager(self, user_name, manager_id):
318
+ """
319
+ 更新店铺的店长ID
320
+
321
+ Args:
322
+ user_name (str): 店铺账号
323
+ manager_id (int): 店长ID(用户表ID)
324
+
325
+ Returns:
326
+ bool: 是否成功
327
+ """
328
+ session = self.Session()
329
+ try:
330
+ store = session.query(SheinStore).filter(
331
+ SheinStore.user_name == user_name
332
+ ).first()
333
+
334
+ if store:
335
+ store.store_manager_id = manager_id
336
+ store.updated_at = datetime.now()
337
+ session.commit()
338
+ print(f"成功更新店长ID: {user_name} -> {manager_id}")
339
+ return True
340
+ else:
341
+ print(f"未找到店铺: {user_name}")
342
+ return False
343
+ except Exception as e:
344
+ session.rollback()
345
+ print(f"更新店长ID失败: {e}")
346
+ raise
347
+ finally:
348
+ session.close()
349
+
350
+ def soft_delete_store(self, user_name):
351
+ """
352
+ 软删除指定店铺(设置is_deleted=1)
353
+
354
+ Args:
355
+ user_name (str): 店铺账号
356
+
357
+ Returns:
358
+ bool: 是否成功
359
+ """
360
+ session = self.Session()
361
+ try:
362
+ store = session.query(SheinStore).filter(
363
+ SheinStore.user_name == user_name
364
+ ).first()
365
+
366
+ if store:
367
+ store.is_deleted = 1
368
+ store.updated_at = datetime.now()
369
+ session.commit()
370
+ print(f"成功软删除店铺: {user_name}")
371
+ return True
372
+ else:
373
+ print(f"未找到店铺: {user_name}")
374
+ return False
375
+ except Exception as e:
376
+ session.rollback()
377
+ print(f"软删除失败: {e}")
378
+ raise
379
+ finally:
380
+ session.close()
381
+
382
+ def restore_store(self, user_name):
383
+ """
384
+ 恢复软删除的店铺(设置is_deleted=0)
385
+
386
+ Args:
387
+ user_name (str): 店铺账号
388
+
389
+ Returns:
390
+ bool: 是否成功
391
+ """
392
+ session = self.Session()
393
+ try:
394
+ store = session.query(SheinStore).filter(
395
+ SheinStore.user_name == user_name
396
+ ).first()
397
+
398
+ if store:
399
+ store.is_deleted = 0
400
+ store.updated_at = datetime.now()
401
+ session.commit()
402
+ print(f"成功恢复店铺: {user_name}")
403
+ return True
404
+ else:
405
+ print(f"未找到店铺: {user_name}")
406
+ return False
407
+ except Exception as e:
408
+ session.rollback()
409
+ print(f"恢复失败: {e}")
410
+ raise
411
+ finally:
412
+ session.close()
413
+
414
+ def delete_store(self, user_name):
415
+ """
416
+ 物理删除指定店铺(真正从数据库删除,慎用!)
417
+
418
+ Args:
419
+ user_name (str): 店铺账号
420
+
421
+ Returns:
422
+ int: 删除的记录数
423
+ """
424
+ session = self.Session()
425
+ try:
426
+ delete_count = session.query(SheinStore).filter(
427
+ SheinStore.user_name == user_name
428
+ ).delete()
429
+ session.commit()
430
+ print(f"成功物理删除 {delete_count} 条记录")
431
+ return delete_count
432
+ except Exception as e:
433
+ session.rollback()
434
+ print(f"删除数据失败: {e}")
435
+ raise
436
+ finally:
437
+ session.close()
438
+
439
+
440
+ # 创建全局实例,用于快速访问
441
+ _shein_store_manager = None
442
+
443
+
444
+ def get_shein_store_manager(database_url):
445
+ """
446
+ 获取店铺管理器实例
447
+
448
+ Args:
449
+ database_url (str): 数据库连接URL
450
+
451
+ Returns:
452
+ SheinStoreManager: 店铺管理器实例
453
+ """
454
+ global _shein_store_manager
455
+ if _shein_store_manager is None:
456
+ _shein_store_manager = SheinStoreManager(database_url)
457
+ return _shein_store_manager
458
+
459
+
460
+ def insert_store_data(database_url, data_list):
461
+ """
462
+ 插入店铺数据的便捷函数
463
+
464
+ Args:
465
+ database_url (str): 数据库连接URL
466
+ data_list (list): 数据字典列表
467
+
468
+ Returns:
469
+ int: 成功插入的记录数
470
+ """
471
+ manager = get_shein_store_manager(database_url)
472
+ return manager.insert_data(data_list)
473
+
474
+
475
+ def import_from_json(database_url, json_data):
476
+ """
477
+ 从JSON数据导入的便捷函数(自动识别字典或列表格式)
478
+
479
+ Args:
480
+ database_url (str): 数据库连接URL
481
+ json_data (dict|list): JSON数据(字典或列表)
482
+
483
+ Returns:
484
+ int: 成功导入的记录数
485
+ """
486
+ manager = get_shein_store_manager(database_url)
487
+
488
+ if isinstance(json_data, dict):
489
+ return manager.import_from_json_dict(json_data)
490
+ elif isinstance(json_data, list):
491
+ return manager.import_from_json_list(json_data)
492
+ else:
493
+ raise ValueError("json_data 必须是字典或列表类型")
494
+
495
+
496
+ def get_store_by_username(database_url, user_name, include_deleted=False):
497
+ """
498
+ 根据用户名查询店铺的便捷函数
499
+
500
+ Args:
501
+ database_url (str): 数据库连接URL
502
+ user_name (str): 店铺账号
503
+ include_deleted (bool): 是否包含已删除的店铺,默认False
504
+
505
+ Returns:
506
+ SheinStore: 店铺对象
507
+ """
508
+ manager = get_shein_store_manager(database_url)
509
+ return manager.get_store_by_username(user_name, include_deleted)
510
+
511
+
512
+ def update_store_manager(database_url, user_name, manager_id):
513
+ """
514
+ 更新店长ID的便捷函数
515
+
516
+ Args:
517
+ database_url (str): 数据库连接URL
518
+ user_name (str): 店铺账号
519
+ manager_id (int): 店长ID(用户表ID)
520
+
521
+ Returns:
522
+ bool: 是否成功
523
+ """
524
+ manager = get_shein_store_manager(database_url)
525
+ return manager.update_store_manager(user_name, manager_id)
526
+
527
+
528
+ def soft_delete_store(database_url, user_name):
529
+ """
530
+ 软删除店铺的便捷函数
531
+
532
+ Args:
533
+ database_url (str): 数据库连接URL
534
+ user_name (str): 店铺账号
535
+
536
+ Returns:
537
+ bool: 是否成功
538
+ """
539
+ manager = get_shein_store_manager(database_url)
540
+ return manager.soft_delete_store(user_name)
541
+
542
+
543
+ def restore_store(database_url, user_name):
544
+ """
545
+ 恢复店铺的便捷函数
546
+
547
+ Args:
548
+ database_url (str): 数据库连接URL
549
+ user_name (str): 店铺账号
550
+
551
+ Returns:
552
+ bool: 是否成功
553
+ """
554
+ manager = get_shein_store_manager(database_url)
555
+ return manager.restore_store(user_name)
556
+
557
+
558
+ if __name__ == '__main__':
559
+ # 测试代码
560
+ # 注意:需要提供数据库连接URL
561
+ database_url = "mysql+pymysql://root:123wyk@127.0.0.1:3306/lz"
562
+
563
+ manager = SheinStoreManager(database_url)
564
+
565
+ # 创建表
566
+ manager.create_table()
567
+
568
+ # 读取JSON文件
569
+ with open('../../docs/shein_user.json', 'r', encoding='utf-8') as f:
570
+ json_data = json.load(f)
571
+
572
+ count = manager.import_from_json_dict(json_data)
573
+ print(f"成功导入 {count} 条店铺记录")
574
+
575
+ # 更新店长ID(手动设置,后续导入不会覆盖)
576
+ # manager.update_store_manager('GS0628340', 123) # 123是用户表的ID
577
+
578
+ # 软删除测试
579
+ # manager.soft_delete_store('GS0628340')
580
+
581
+ # 查询店铺(默认不包含已删除)
582
+ # store = manager.get_store_by_username('GS0628340')
583
+ # print(f"查询结果: {store}")
584
+
585
+ # 查询店铺(包含已删除)
586
+ # store = manager.get_store_by_username('GS0628340', include_deleted=True)
587
+ # print(f"查询结果(包含已删除): {store}")
588
+
589
+ # 恢复店铺
590
+ # manager.restore_store('GS0628340')
591
+
592
+ # 按类目查询示例
593
+ # stores = manager.get_stores_by_category(lv2_category_name='全托管-备CN-中国团队-内睡服装')
594
+ # print(f"找到 {len(stores)} 个店铺")
@@ -1236,7 +1236,10 @@ class SheinLib:
1236
1236
  for prom_inf_ing in skc_item['promCampaign'].get('promInfIng') or []:
1237
1237
  prom_id = prom_inf_ing['promId']
1238
1238
  log('prom_id:', prom_id, len(prom_id))
1239
- if len(prom_id) >= 6:
1239
+ if len(prom_id) >= 11:
1240
+ # 托管活动
1241
+ prom_inf_ing['promDetail'] = self.get_skc_activity_price_info(skc, prom_id)
1242
+ elif len(prom_id) >= 8:
1240
1243
  # 营销工具
1241
1244
  prom_inf_ing['promDetail'] = self.query_goods_detail(prom_id)
1242
1245
  else:
@@ -1246,7 +1249,9 @@ class SheinLib:
1246
1249
  for prom_inf_ready in skc_item['promCampaign'].get('promInfReady') or []:
1247
1250
  prom_id = prom_inf_ready['promId']
1248
1251
  log('prom_id:', prom_id, len(prom_id))
1249
- if len(prom_id) >= 6:
1252
+ if len(prom_id) >= 11:
1253
+ prom_inf_ready['promDetail'] = self.get_skc_activity_price_info(skc, prom_id)
1254
+ elif len(prom_id) >= 8:
1250
1255
  prom_inf_ready['promDetail'] = self.query_goods_detail(prom_id)
1251
1256
  else:
1252
1257
  prom_inf_ready['promDetail'] = self.get_partake_activity_detail(prom_id, skc)
@@ -2373,6 +2378,7 @@ class SheinLib:
2373
2378
  list_item += response_text['info']['data']
2374
2379
  time.sleep(0.1)
2375
2380
 
2381
+ log(list_item)
2376
2382
  write_dict_to_file(cache_file, list_item)
2377
2383
  return list_item
2378
2384
 
@@ -2398,6 +2404,7 @@ class SheinLib:
2398
2404
  raise send_exception(json.dumps(response_text, ensure_ascii=False))
2399
2405
  list_item = response_text['info']['data']
2400
2406
 
2407
+ log(list_item)
2401
2408
  write_dict_to_file(cache_file, list_item)
2402
2409
  return list_item
2403
2410
 
@@ -3930,3 +3937,247 @@ class SheinLib:
3930
3937
  log('临时下载文件已清理')
3931
3938
 
3932
3939
  return output_file_path
3940
+
3941
+ def query_hosting_info_list(self):
3942
+ """
3943
+ 查询店铺活动托管规则列表
3944
+
3945
+ Returns:
3946
+ list: 托管规则列表,每个元素包含:
3947
+ - hosting_id: 托管规则ID
3948
+ - scene_type: 场景类型
3949
+ - state: 状态
3950
+ - hosting_name: 托管规则名称
3951
+ - hosting_tools_id: 托管工具ID
3952
+ - hosting_tools_state: 托管工具状态
3953
+ - time_zone: 时区
3954
+ - create_user: 创建用户
3955
+ - last_update_user: 最后更新用户
3956
+ - insert_time: 创建时间
3957
+ - last_update_time: 最后更新时间
3958
+ - exist_act_goods: 是否存在活动商品
3959
+ """
3960
+ log(f'正在获取 {self.store_name} 店铺活动托管规则列表')
3961
+
3962
+ cache_file = f'{self.config.auto_dir}/shein/cache/hosting_info_list_{self.store_username}_{TimeUtils.today_date()}.json'
3963
+ hosting_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 8)
3964
+ if len(hosting_list) > 0:
3965
+ log('返回缓存数据')
3966
+ return hosting_list
3967
+
3968
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/hosting/query_hosting_info_list"
3969
+ payload = {}
3970
+
3971
+ response_text = fetch(self.web_page, url, payload)
3972
+ error_code = response_text.get('code')
3973
+ if str(error_code) != '0':
3974
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
3975
+
3976
+ hosting_list = response_text.get('info', [])
3977
+ log(f'获取到 {len(hosting_list)} 条托管规则')
3978
+
3979
+ write_dict_to_file_ex(cache_file, {self.store_username: hosting_list}, [self.store_username])
3980
+
3981
+ return hosting_list
3982
+
3983
+ def query_hosting_activity_goods(self, hosting_id, goods_states=None):
3984
+ """
3985
+ 查询托管活动参与的商品
3986
+
3987
+ Args:
3988
+ hosting_id: 托管规则ID
3989
+ goods_states: 商品状态列表,默认为[1](在售)
3990
+
3991
+ Returns:
3992
+ list: 参与托管活动的商品列表,每个元素包含:
3993
+ - goods_state: 商品状态
3994
+ - skc_info_list: SKC信息列表
3995
+ - skc_id: SKC ID
3996
+ - skc_name: SKC名称
3997
+ - goods_name: 商品名称
3998
+ - image_url: 图片URL
3999
+ - act_stock_num: 活动库存数量
4000
+ - act_sales_num: 活动销量
4001
+ - activity_info: 活动信息
4002
+ - sku_info_list: SKU信息列表
4003
+ """
4004
+ if goods_states is None:
4005
+ goods_states = [1]
4006
+
4007
+ log(f'正在获取 {self.store_name} 托管活动商品列表 hosting_id={hosting_id}')
4008
+
4009
+ cache_file = f'{self.config.auto_dir}/shein/cache/hosting_activity_goods_{self.store_username}_{hosting_id}_{TimeUtils.today_date()}.json'
4010
+ goods_list = read_dict_from_file_ex(cache_file, self.store_username, 3600 * 8)
4011
+ if len(goods_list) > 0:
4012
+ log('返回缓存数据')
4013
+ return goods_list
4014
+
4015
+ page_num = 1
4016
+ page_size = 100
4017
+
4018
+ url = "https://sso.geiwohuo.com/mrs-api-prefix/promotion/hosting/query_hosting_activity_goods"
4019
+ payload = {
4020
+ "goods_states": goods_states,
4021
+ "hosting_id" : str(hosting_id),
4022
+ "page_num" : page_num,
4023
+ "page_size" : page_size
4024
+ }
4025
+
4026
+ response_text = fetch(self.web_page, url, payload)
4027
+ error_code = response_text.get('code')
4028
+ if str(error_code) != '0':
4029
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
4030
+
4031
+ goods_list = response_text.get('info', [])
4032
+ if not goods_list:
4033
+ log('未获取到商品数据')
4034
+ return []
4035
+
4036
+ # 获取第一页数据
4037
+ first_item = goods_list[0] if goods_list else {}
4038
+ skc_info_list = first_item.get('skc_info_list', {})
4039
+ all_data = skc_info_list.get('data', [])
4040
+ meta = skc_info_list.get('meta', {})
4041
+ total = meta.get('count', 0)
4042
+
4043
+ log(f'第1页获取到 {len(all_data)} 条商品,总数: {total}')
4044
+
4045
+ # 如果有多页,继续获取
4046
+ if total > page_size:
4047
+ totalPage = math.ceil(total / page_size)
4048
+ for page in range(2, totalPage + 1):
4049
+ log(f'获取托管活动商品列表 第{page}/{totalPage}页')
4050
+ payload['page_num'] = page
4051
+ response_text = fetch(self.web_page, url, payload)
4052
+ error_code = response_text.get('code')
4053
+ if str(error_code) != '0':
4054
+ log(f'获取第{page}页失败: {response_text}')
4055
+ continue
4056
+
4057
+ page_goods_list = response_text.get('info', [])
4058
+ if page_goods_list:
4059
+ page_data = page_goods_list[0].get('skc_info_list', {}).get('data', [])
4060
+ all_data.extend(page_data)
4061
+ log(f'第{page}页获取到 {len(page_data)} 条商品')
4062
+
4063
+ time.sleep(0.1)
4064
+
4065
+ log(f'总共获取到 {len(all_data)} 条商品')
4066
+
4067
+ # 保存缓存
4068
+ write_dict_to_file_ex(cache_file, {self.store_username: all_data}, [self.store_username])
4069
+
4070
+ return all_data
4071
+
4072
+ def get_skc_activity_price_info(self, skc, activity_id):
4073
+ """
4074
+ 根据SKC和活动ID获取供货价、活动价和活动库存
4075
+
4076
+ Args:
4077
+ skc: SKC名称
4078
+ activity_id: 活动ID(可以是字符串或整数)
4079
+
4080
+ Returns:
4081
+ dict: 包含以下键值的字典,如果未找到则返回None:
4082
+ - sku_price: SKU供货价(取第一个SKU的价格)
4083
+ - act_sku_price: SKU活动价(取第一个SKU的活动价)
4084
+ - act_stock_num: 活动库存数量
4085
+ - skc_name: SKC名称
4086
+ - goods_name: 商品名称
4087
+ - activity_id: 活动ID
4088
+ - currency: 币种
4089
+ - image_url: 商品图片
4090
+ """
4091
+ log(f'获取SKC活动价格信息: skc={skc}, activity_id={activity_id}')
4092
+
4093
+ # 转换activity_id为整数进行比较
4094
+ try:
4095
+ target_activity_id = int(activity_id)
4096
+ except (ValueError, TypeError):
4097
+ log(f'无效的activity_id: {activity_id}')
4098
+ return None
4099
+
4100
+ # 缓存文件,使用skc和activity_id作为缓存key
4101
+ cache_file = f'{self.config.auto_dir}/shein/cache/skc_activity_price_{self.store_username}_{skc}_{activity_id}_{TimeUtils.today_date()}.json'
4102
+ cached_data = read_dict_from_file(cache_file, 3600 * 8)
4103
+ if cached_data:
4104
+ log('返回缓存的价格信息')
4105
+ return cached_data
4106
+
4107
+ # 获取所有托管规则
4108
+ hosting_list = self.query_hosting_info_list()
4109
+
4110
+ if not hosting_list:
4111
+ log('未找到任何托管规则')
4112
+ return None
4113
+
4114
+ # 遍历所有托管规则,查找匹配的SKC和活动
4115
+ for hosting in hosting_list:
4116
+ hosting_id = hosting.get('hosting_id')
4117
+ if not hosting_id:
4118
+ continue
4119
+
4120
+ log(f'查询托管规则: hosting_id={hosting_id}, hosting_name={hosting.get("hosting_name")}')
4121
+
4122
+ # 获取该托管规则下的商品
4123
+ goods_list = self.query_hosting_activity_goods(hosting_id)
4124
+
4125
+ # 在商品列表中查找匹配的SKC
4126
+ for goods_item in goods_list:
4127
+ skc_name = goods_item.get('skc_name', '')
4128
+
4129
+ # 匹配SKC名称
4130
+ if skc_name != skc:
4131
+ continue
4132
+
4133
+ # 检查活动信息
4134
+ activity_info = goods_item.get('activity_info', {})
4135
+ goods_activity_id = activity_info.get('activity_id')
4136
+
4137
+ # 匹配活动ID
4138
+ try:
4139
+ if int(goods_activity_id) != target_activity_id:
4140
+ continue
4141
+ except (ValueError, TypeError):
4142
+ continue
4143
+
4144
+ log(f'找到匹配的SKC: {skc_name}, activity_id={goods_activity_id}')
4145
+
4146
+ # 提取活动库存
4147
+ act_stock_num = goods_item.get('act_stock_num', 0)
4148
+
4149
+ # 获取第一个SKU的价格信息
4150
+ sku_info_list = goods_item.get('sku_info_list', [])
4151
+ if not sku_info_list:
4152
+ log(f'SKC {skc_name} 没有SKU信息')
4153
+ continue
4154
+
4155
+ first_sku = sku_info_list[0]
4156
+ sku_price = first_sku.get('sku_price', 0)
4157
+ act_sku_price = first_sku.get('act_sku_price', 0)
4158
+ currency = first_sku.get('currency', 'CNY')
4159
+
4160
+ # 构建返回结果
4161
+ result = {
4162
+ 'skc_name' : skc_name,
4163
+ 'goods_name' : goods_item.get('goods_name', ''),
4164
+ 'image_url' : goods_item.get('image_url', ''),
4165
+ 'activity_id' : goods_activity_id,
4166
+ 'act_stock_num': act_stock_num,
4167
+ 'sku_price' : sku_price,
4168
+ 'act_sku_price': act_sku_price,
4169
+ 'currency' : currency,
4170
+ 'start_time' : activity_info.get('start_time', ''),
4171
+ 'end_time' : activity_info.get('end_time', ''),
4172
+ 'time_zone' : activity_info.get('time_zone', ''),
4173
+ }
4174
+
4175
+ log(f'SKC供货价: {sku_price} {currency}, 活动价: {act_sku_price} {currency}, 活动库存: {act_stock_num}')
4176
+
4177
+ # 保存缓存
4178
+ write_dict_to_file(cache_file, result)
4179
+
4180
+ return result
4181
+
4182
+ log(f'未找到匹配的SKC和活动: skc={skc}, activity_id={activity_id}')
4183
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.1.34
3
+ Version: 1.1.36
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -33,5 +33,6 @@ qrpa/mysql_module/new_product_analysis_model.py
33
33
  qrpa/mysql_module/shein_ledger_model.py
34
34
  qrpa/mysql_module/shein_product_model.py
35
35
  qrpa/mysql_module/shein_return_order_model.py
36
+ qrpa/mysql_module/shein_store_model.py
36
37
  tests/test_db_migrator.py
37
38
  tests/test_wxwork.py
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
File without changes
File without changes
File without changes