qrpa 1.1.13__tar.gz → 1.1.15__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 (38) hide show
  1. {qrpa-1.1.13 → qrpa-1.1.15}/PKG-INFO +1 -1
  2. {qrpa-1.1.13 → qrpa-1.1.15}/pyproject.toml +1 -1
  3. qrpa-1.1.15/qrpa/mysql_module/shein_ledger_model.py +468 -0
  4. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_excel.py +8 -4
  5. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_lib.py +47 -0
  6. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_mysql.py +7 -0
  7. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/time_utils.py +28 -0
  8. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa.egg-info/PKG-INFO +1 -1
  9. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa.egg-info/SOURCES.txt +1 -0
  10. {qrpa-1.1.13 → qrpa-1.1.15}/README.md +0 -0
  11. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/RateLimitedSender.py +0 -0
  12. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/__init__.py +0 -0
  13. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/db_migrator.py +0 -0
  14. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/feishu_bot_app.py +0 -0
  15. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/feishu_client.py +0 -0
  16. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/feishu_logic.py +0 -0
  17. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/fun_base.py +0 -0
  18. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/fun_excel.py +0 -0
  19. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/fun_file.py +0 -0
  20. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/fun_web.py +0 -0
  21. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/fun_win.py +0 -0
  22. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/mysql_module/__init__.py +0 -0
  23. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/mysql_module/shein_product_model.py +0 -0
  24. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/mysql_module/shein_return_order_model.py +0 -0
  25. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_daily_report_model.py +0 -0
  26. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_sqlite.py +0 -0
  27. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/shein_ziniao.py +0 -0
  28. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/temu_chrome.py +0 -0
  29. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/temu_excel.py +0 -0
  30. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/temu_lib.py +0 -0
  31. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/time_utils_example.py +0 -0
  32. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa/wxwork.py +0 -0
  33. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa.egg-info/dependency_links.txt +0 -0
  34. {qrpa-1.1.13 → qrpa-1.1.15}/qrpa.egg-info/top_level.txt +0 -0
  35. {qrpa-1.1.13 → qrpa-1.1.15}/setup.cfg +0 -0
  36. {qrpa-1.1.13 → qrpa-1.1.15}/setup.py +0 -0
  37. {qrpa-1.1.13 → qrpa-1.1.15}/tests/test_db_migrator.py +0 -0
  38. {qrpa-1.1.13 → qrpa-1.1.15}/tests/test_wxwork.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.1.13
3
+ Version: 1.1.15
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.13"
7
+ version = "1.1.15"
8
8
  description = "qsir's rpa library"
9
9
  authors = [{ name = "QSir", email = "1171725650@qq.com" }]
10
10
  readme = "README.md"
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ SHEIN账本记录数据模型
5
+ 使用SQLAlchemy定义账本记录表结构
6
+ """
7
+
8
+ from sqlalchemy import create_engine, Column, String, Text, DateTime, Integer, DECIMAL, 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 SheinLedgerRecord(Base):
18
+ """
19
+ SHEIN账本记录表
20
+ 存储来自接口的账本记录数据
21
+ """
22
+ __tablename__ = 'shein_ledger_records'
23
+
24
+ # 主键ID (自增)
25
+ id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
26
+
27
+ # 原始数据中的ID作为ledger_id
28
+ ledger_id = Column(String(50), nullable=False, unique=True, comment='账本记录ID(原始数据中的id字段)')
29
+
30
+ # 变更类型相关字段
31
+ display_change_type = Column(Integer, nullable=True, comment='显示变更类型编码')
32
+ display_change_type_name = Column(String(100), nullable=True, comment='显示变更类型名称')
33
+ change_type = Column(Integer, nullable=True, comment='变更类型编码')
34
+
35
+ # 结算类型相关字段
36
+ settle_type = Column(Integer, nullable=True, comment='结算类型编码')
37
+ settle_type_name = Column(String(100), nullable=True, comment='结算类型名称')
38
+
39
+ # 业务单号相关字段
40
+ business_no = Column(String(100), nullable=True, comment='业务单号')
41
+ bill_no = Column(String(100), nullable=True, comment='账单编号')
42
+ uniq_key = Column(String(100), nullable=True, comment='唯一键')
43
+
44
+ # 时间相关字段
45
+ happen_time = Column(DateTime, nullable=True, comment='发生时间')
46
+ add_time = Column(DateTime, nullable=True, comment='添加时间')
47
+
48
+ # 供应商相关字段
49
+ supplier_id = Column(Integer, nullable=True, comment='供应商ID')
50
+ supplier_name = Column(String(200), nullable=True, comment='供应商名称')
51
+ supplier_code = Column(String(100), nullable=True, comment='供应商编码')
52
+
53
+ # 商品相关字段
54
+ skc = Column(String(100), nullable=True, comment='SKC编码')
55
+ sku = Column(String(100), nullable=True, comment='SKU编码')
56
+ supplier_sku = Column(String(200), nullable=True, comment='供应商SKU')
57
+ suffix_zh = Column(String(100), nullable=True, comment='商品属性(中文)')
58
+
59
+ # 数量和金额相关字段
60
+ quantity = Column(Integer, nullable=True, comment='数量')
61
+ cost = Column(DECIMAL(10, 2), nullable=True, comment='成本')
62
+ amount = Column(DECIMAL(10, 2), nullable=True, comment='金额')
63
+ currency = Column(String(10), nullable=True, comment='货币类型')
64
+
65
+ # 备注
66
+ remark = Column(Text, nullable=True, comment='备注')
67
+
68
+ # 前后关联字段
69
+ before_inventory_id = Column(String(50), nullable=True, comment='前库存ID')
70
+ after_inventory_id = Column(String(50), nullable=True, comment='后库存ID')
71
+ before_business_no = Column(String(100), nullable=True, comment='前业务单号')
72
+ after_business_no = Column(String(100), nullable=True, comment='后业务单号')
73
+ before_bill_no = Column(String(100), nullable=True, comment='前账单号')
74
+ after_bill_no = Column(String(100), nullable=True, comment='后账单号')
75
+ after_change_type_name = Column(String(100), nullable=True, comment='后变更类型名称')
76
+
77
+ # 业务标签
78
+ business_no_tags = Column(String(500), nullable=True, comment='业务单号标签')
79
+
80
+ # 来源系统
81
+ source_system = Column(String(50), nullable=True, comment='来源系统')
82
+
83
+ # 促销相关
84
+ show_promotion = Column(Integer, nullable=True, comment='显示促销')
85
+
86
+ # 销售相关字段
87
+ sales_seller_id = Column(Integer, nullable=True, comment='销售卖家ID')
88
+ sales_seller_name = Column(String(200), nullable=True, comment='销售卖家名称')
89
+
90
+ # 店铺相关字段
91
+ store_username = Column(String(100), nullable=True, comment='店铺用户名')
92
+ store_name = Column(String(200), nullable=True, comment='店铺名称')
93
+ store_manager = Column(String(100), nullable=True, comment='店铺经理')
94
+
95
+ # 成本价格和SKU图片
96
+ cost_price = Column(DECIMAL(10, 2), nullable=True, comment='成本价格')
97
+ sku_img = Column(Text, nullable=True, comment='SKU图片URL')
98
+
99
+ # 时间戳
100
+ created_at = Column(DateTime, default=datetime.now, comment='创建时间')
101
+ updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')
102
+
103
+ # 定义索引
104
+ __table_args__ = (
105
+ Index('ix_ledger_id', 'ledger_id'),
106
+ Index('ix_business_no', 'business_no'),
107
+ Index('ix_bill_no', 'bill_no'),
108
+ Index('ix_supplier_id', 'supplier_id'),
109
+ Index('ix_skc', 'skc'),
110
+ Index('ix_sku', 'sku'),
111
+ Index('ix_store_username', 'store_username'),
112
+ Index('ix_happen_time', 'happen_time'),
113
+ Index('ix_add_time', 'add_time'),
114
+ Index('ix_change_type', 'change_type'),
115
+ Index('ix_display_change_type', 'display_change_type'),
116
+ )
117
+
118
+ def __repr__(self):
119
+ return f"<SheinLedgerRecord(id={self.id}, ledger_id='{self.ledger_id}', business_no='{self.business_no}')>"
120
+
121
+ class SheinLedgerManager:
122
+ """
123
+ SHEIN账本记录数据管理器
124
+ 提供数据库操作相关方法
125
+ """
126
+
127
+ def __init__(self, database_url):
128
+ """
129
+ 初始化数据库连接
130
+
131
+ Args:
132
+ database_url (str): 数据库连接URL,例如:
133
+ mysql+pymysql://username:password@localhost:3306/database_name
134
+ """
135
+ self.engine = create_engine(database_url, echo=False)
136
+ self.Session = sessionmaker(bind=self.engine)
137
+
138
+ def create_tables(self):
139
+ """
140
+ 创建数据表
141
+ """
142
+ Base.metadata.create_all(self.engine)
143
+ print("账本记录数据表创建成功!")
144
+
145
+ def drop_tables(self):
146
+ """
147
+ 删除数据表
148
+ """
149
+ Base.metadata.drop_all(self.engine)
150
+ print("账本记录数据表删除成功!")
151
+
152
+ def _parse_datetime(self, datetime_str):
153
+ """
154
+ 解析日期时间字符串
155
+ """
156
+ if not datetime_str:
157
+ return None
158
+ try:
159
+ return datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')
160
+ except:
161
+ return None
162
+
163
+ def _parse_decimal(self, value):
164
+ """
165
+ 解析decimal值
166
+ """
167
+ if value is None:
168
+ return None
169
+ try:
170
+ return float(value)
171
+ except:
172
+ return None
173
+
174
+ def _parse_cost_price(self, value):
175
+ """
176
+ 解析成本价格,如果是"-"或"未匹配到"等非数字值则返回0
177
+ """
178
+ if value is None:
179
+ return 0.0
180
+
181
+ # 转换为字符串处理
182
+ str_value = str(value).strip()
183
+
184
+ # 如果是 "-" 或包含 "未匹配到" 等文本,则返回0
185
+ if str_value == "-" or "未匹配到" in str_value or str_value == "":
186
+ return 0.0
187
+
188
+ try:
189
+ return float(str_value)
190
+ except:
191
+ return 0.0
192
+
193
+ def upsert_ledger_data(self, data_list):
194
+ """
195
+ 从JSON数据中执行upsert操作(插入或更新)
196
+
197
+ Args:
198
+ data_list (list): 账本记录数据列表
199
+ """
200
+ session = self.Session()
201
+ try:
202
+ insert_count = 0
203
+ update_count = 0
204
+
205
+ for data in data_list:
206
+ ledger_id = str(data.get('id'))
207
+ existing_record = session.query(SheinLedgerRecord).filter_by(ledger_id=ledger_id).first()
208
+
209
+ if existing_record:
210
+ # 更新现有记录
211
+ self._update_record_from_data(existing_record, data)
212
+ update_count += 1
213
+ else:
214
+ # 插入新记录
215
+ new_record = self._create_record_from_data(data)
216
+ session.add(new_record)
217
+ insert_count += 1
218
+
219
+ session.commit()
220
+ print(f"成功处理 {len(data_list)} 条账本记录数据")
221
+ print(f"新增记录: {insert_count} 条,更新记录: {update_count} 条")
222
+
223
+ except Exception as e:
224
+ session.rollback()
225
+ print(f"处理数据失败: {e}")
226
+ raise
227
+ finally:
228
+ session.close()
229
+
230
+ def _create_record_from_data(self, data):
231
+ """
232
+ 从JSON数据创建新的记录对象
233
+ """
234
+ return SheinLedgerRecord(
235
+ ledger_id=str(data.get('id')),
236
+ display_change_type=data.get('displayChangeType'),
237
+ display_change_type_name=data.get('displayChangeTypeName'),
238
+ change_type=data.get('changeType'),
239
+ settle_type=data.get('settleType'),
240
+ settle_type_name=data.get('settleTypeName'),
241
+ business_no=data.get('businessNo'),
242
+ bill_no=data.get('billNo'),
243
+ uniq_key=data.get('uniqKey'),
244
+ happen_time=self._parse_datetime(data.get('happenTime')),
245
+ add_time=self._parse_datetime(data.get('addTime')),
246
+ supplier_id=data.get('supplierId'),
247
+ supplier_name=data.get('supplierName'),
248
+ supplier_code=data.get('supplierCode'),
249
+ skc=data.get('skc'),
250
+ sku=data.get('sku'),
251
+ supplier_sku=data.get('supplierSku'),
252
+ suffix_zh=data.get('suffixZh'),
253
+ quantity=data.get('quantity'),
254
+ cost=self._parse_decimal(data.get('cost')),
255
+ amount=self._parse_decimal(data.get('amount')),
256
+ currency=data.get('currency'),
257
+ remark=data.get('remark'),
258
+ before_inventory_id=data.get('beforeInventoryId'),
259
+ after_inventory_id=data.get('afterInventoryId'),
260
+ before_business_no=data.get('beforeBusinessNo'),
261
+ after_business_no=data.get('afterBusinessNo'),
262
+ before_bill_no=data.get('beforeBillNo'),
263
+ after_bill_no=data.get('afterBillNo'),
264
+ after_change_type_name=data.get('afterChangeTypeName'),
265
+ business_no_tags=data.get('businessNoTags'),
266
+ source_system=data.get('sourceSystem'),
267
+ show_promotion=data.get('showPromotion'),
268
+ sales_seller_id=data.get('salesSellerId'),
269
+ sales_seller_name=data.get('salesSellerName'),
270
+ store_username=data.get('store_username'),
271
+ store_name=data.get('store_name'),
272
+ store_manager=data.get('store_manager'),
273
+ cost_price=self._parse_cost_price(data.get('cost_price')),
274
+ sku_img=data.get('sku_img')
275
+ )
276
+
277
+ def _update_record_from_data(self, record, data):
278
+ """
279
+ 使用JSON数据更新现有记录
280
+ """
281
+ record.display_change_type = data.get('displayChangeType')
282
+ record.display_change_type_name = data.get('displayChangeTypeName')
283
+ record.change_type = data.get('changeType')
284
+ record.settle_type = data.get('settleType')
285
+ record.settle_type_name = data.get('settleTypeName')
286
+ record.business_no = data.get('businessNo')
287
+ record.bill_no = data.get('billNo')
288
+ record.uniq_key = data.get('uniqKey')
289
+ record.happen_time = self._parse_datetime(data.get('happenTime'))
290
+ record.add_time = self._parse_datetime(data.get('addTime'))
291
+ record.supplier_id = data.get('supplierId')
292
+ record.supplier_name = data.get('supplierName')
293
+ record.supplier_code = data.get('supplierCode')
294
+ record.skc = data.get('skc')
295
+ record.sku = data.get('sku')
296
+ record.supplier_sku = data.get('supplierSku')
297
+ record.suffix_zh = data.get('suffixZh')
298
+ record.quantity = data.get('quantity')
299
+ record.cost = self._parse_decimal(data.get('cost'))
300
+ record.amount = self._parse_decimal(data.get('amount'))
301
+ record.currency = data.get('currency')
302
+ record.remark = data.get('remark')
303
+ record.before_inventory_id = data.get('beforeInventoryId')
304
+ record.after_inventory_id = data.get('afterInventoryId')
305
+ record.before_business_no = data.get('beforeBusinessNo')
306
+ record.after_business_no = data.get('afterBusinessNo')
307
+ record.before_bill_no = data.get('beforeBillNo')
308
+ record.after_bill_no = data.get('afterBillNo')
309
+ record.after_change_type_name = data.get('afterChangeTypeName')
310
+ record.business_no_tags = data.get('businessNoTags')
311
+ record.source_system = data.get('sourceSystem')
312
+ record.show_promotion = data.get('showPromotion')
313
+ record.sales_seller_id = data.get('salesSellerId')
314
+ record.sales_seller_name = data.get('salesSellerName')
315
+ record.store_username = data.get('store_username')
316
+ record.store_name = data.get('store_name')
317
+ record.store_manager = data.get('store_manager')
318
+ record.cost_price = self._parse_cost_price(data.get('cost_price'))
319
+ record.sku_img = data.get('sku_img')
320
+ record.updated_at = datetime.now()
321
+
322
+ def get_ledger_records(self, limit=None, offset=None, order_by=None):
323
+ """
324
+ 查询账本记录列表
325
+
326
+ Args:
327
+ limit (int): 限制返回数量
328
+ offset (int): 偏移量
329
+ order_by (str): 排序字段,默认按happen_time降序
330
+
331
+ Returns:
332
+ list: 账本记录列表
333
+ """
334
+ session = self.Session()
335
+ try:
336
+ query = session.query(SheinLedgerRecord)
337
+
338
+ # 默认按发生时间降序排列
339
+ if order_by:
340
+ if hasattr(SheinLedgerRecord, order_by):
341
+ query = query.order_by(getattr(SheinLedgerRecord, order_by).desc())
342
+ else:
343
+ query = query.order_by(SheinLedgerRecord.happen_time.desc())
344
+
345
+ if offset:
346
+ query = query.offset(offset)
347
+ if limit:
348
+ query = query.limit(limit)
349
+ return query.all()
350
+ finally:
351
+ session.close()
352
+
353
+ def search_records(self, **kwargs):
354
+ """
355
+ 根据条件搜索账本记录
356
+
357
+ Args:
358
+ **kwargs: 搜索条件,如store_username, business_no, sku等
359
+
360
+ Returns:
361
+ list: 符合条件的记录列表
362
+ """
363
+ session = self.Session()
364
+ try:
365
+ query = session.query(SheinLedgerRecord)
366
+
367
+ # 根据传入的条件进行过滤
368
+ for key, value in kwargs.items():
369
+ if hasattr(SheinLedgerRecord, key) and value is not None:
370
+ if isinstance(value, str) and key in ['business_no', 'bill_no', 'skc', 'sku', 'supplier_name', 'store_name']:
371
+ # 字符串字段支持模糊搜索
372
+ query = query.filter(getattr(SheinLedgerRecord, key).like(f'%{value}%'))
373
+ else:
374
+ query = query.filter(getattr(SheinLedgerRecord, key) == value)
375
+
376
+ return query.order_by(SheinLedgerRecord.happen_time.desc()).all()
377
+ finally:
378
+ session.close()
379
+
380
+ def get_statistics_by_store(self, store_username=None):
381
+ """
382
+ 按店铺统计数据
383
+
384
+ Args:
385
+ store_username (str): 店铺用户名,如果不提供则统计所有店铺
386
+
387
+ Returns:
388
+ dict: 统计结果
389
+ """
390
+ session = self.Session()
391
+ try:
392
+ query = session.query(SheinLedgerRecord)
393
+
394
+ if store_username:
395
+ query = query.filter(SheinLedgerRecord.store_username == store_username)
396
+
397
+ records = query.all()
398
+
399
+ # 统计信息
400
+ total_count = len(records)
401
+ total_amount = sum([r.amount for r in records if r.amount])
402
+ total_cost = sum([r.cost for r in records if r.cost])
403
+ total_quantity = sum([r.quantity for r in records if r.quantity])
404
+
405
+ # 按变更类型统计
406
+ change_type_stats = {}
407
+ for record in records:
408
+ change_type = record.display_change_type_name or '未知'
409
+ if change_type not in change_type_stats:
410
+ change_type_stats[change_type] = {'count': 0, 'amount': 0, 'quantity': 0}
411
+ change_type_stats[change_type]['count'] += 1
412
+ change_type_stats[change_type]['amount'] += record.amount or 0
413
+ change_type_stats[change_type]['quantity'] += record.quantity or 0
414
+
415
+ return {
416
+ 'total_count' : total_count,
417
+ 'total_amount' : float(total_amount) if total_amount else 0,
418
+ 'total_cost' : float(total_cost) if total_cost else 0,
419
+ 'total_quantity' : total_quantity,
420
+ 'change_type_stats': change_type_stats
421
+ }
422
+ finally:
423
+ session.close()
424
+
425
+ def import_from_json_file(self, json_file_path):
426
+ """
427
+ 从JSON文件导入数据
428
+
429
+ Args:
430
+ json_file_path (str): JSON文件路径
431
+ """
432
+ with open(json_file_path, 'r', encoding='utf-8') as f:
433
+ data_list = json.load(f)
434
+ self.upsert_ledger_data(data_list)
435
+
436
+ def example_usage():
437
+ """
438
+ 使用示例
439
+ """
440
+ # 数据库连接URL(请根据实际情况修改)
441
+ database_url = "mysql+pymysql://root:123wyk@localhost:3306/lz"
442
+
443
+ # 创建管理器实例
444
+ manager = SheinLedgerManager(database_url)
445
+
446
+ # 创建数据表
447
+ manager.create_tables()
448
+
449
+ # 从JSON文件导入数据
450
+ json_file = "ledger_record_GS0365305_2025-09-24_2025-09-24.json"
451
+ manager.import_from_json_file(json_file)
452
+
453
+ # 查询示例
454
+ records = manager.get_ledger_records(limit=10)
455
+ for record in records:
456
+ print(f"记录ID: {record.ledger_id}, 业务单号: {record.business_no}, 金额: {record.amount}")
457
+
458
+ # 搜索示例
459
+ search_results = manager.search_records(store_username="GS0365305")
460
+ print(f"店铺 GS0365305 的记录数量: {len(search_results)}")
461
+
462
+ # 统计示例
463
+ stats = manager.get_statistics_by_store("GS0365305")
464
+ print(f"统计结果: {stats}")
465
+
466
+ if __name__ == "__main__":
467
+ pass
468
+ # example_usage()
@@ -339,9 +339,13 @@ class SheinExcel:
339
339
  data.sort(key=lambda row: row[8], reverse=True)
340
340
  excel_path = create_file_path(self.config.excel_week_report)
341
341
  sheet_name = '按店铺汇总'
342
+
343
+ date_A = f'新品上架数量({TimeUtils.get_past_nth_day(29, TimeUtils.get_month_first_day())},{TimeUtils.get_past_nth_day(29, TimeUtils.get_yesterday())})'
344
+ date_B = f'成功转备货款({TimeUtils.get_month_first_day()},{TimeUtils.get_yesterday()})'
345
+
342
346
  data.insert(0, ['汇总', '', '', '', '', '', '', '', '', '', '', '', ''])
343
347
  data.insert(0, ['店铺名称', '店铺账号', '店长', '在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '汇总',
344
- '销售出库金额', '新品上架数量', '成功转备货款', '成功率', '导出时间'])
348
+ '销售出库金额', date_A, date_B, '成功率', '导出时间'])
345
349
  write_data(excel_path, sheet_name, data)
346
350
  app, wb, sheet = open_excel(excel_path, sheet_name)
347
351
  beautify_title(sheet)
@@ -349,9 +353,9 @@ class SheinExcel:
349
353
  self.dealFundsExcelFormat(sheet)
350
354
  format_to_percent(sheet, ['成功率'], 0)
351
355
  add_formula_for_column(sheet, '成功率', '=IF(J2=0, 0, k2/J2)', 2)
352
- add_formula_for_column(sheet, '新品上架数量', "=COUNTIF('新品转备货款明细'!A:A, B3)", 3)
353
- add_formula_for_column(sheet, '成功转备货款', "=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)", 3)
354
- add_sum_for_cell(sheet, ['新品上架数量', '成功转备货款'])
356
+ add_formula_for_column(sheet, date_A, "=COUNTIF('新品转备货款明细'!A:A, B3)", 3)
357
+ add_formula_for_column(sheet, date_B, "=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)", 3)
358
+ add_sum_for_cell(sheet, [date_A, date_B])
355
359
  column_to_right(sheet, ['金额', '汇总'])
356
360
  sheet.autofit()
357
361
  delete_sheet_if_exists(wb, 'Sheet1')
@@ -588,6 +588,53 @@ class SheinLib:
588
588
 
589
589
  return list_item
590
590
 
591
+ def get_ledger_record(self, first_day, last_day):
592
+ page_num = 1
593
+ page_size = 200 # 列表最多返回200条数据 大了没有用
594
+
595
+ cache_file = f'{self.config.auto_dir}/shein/ledger/ledger_record_{self.store_username}_{first_day}_{last_day}.json'
596
+ list_item_cache = read_dict_from_file(cache_file)
597
+
598
+ url = f"https://sso.geiwohuo.com/mils/changeDetail/page"
599
+ payload = {
600
+ "displayChangeTypeList": ["6", "7", "9", "10", "11", "12", "13", "16", "18", "19", "21"], # 出库
601
+ "addTimeStart" : f"{first_day} 00:00:00",
602
+ "addTimeEnd" : f"{last_day} 23:59:59",
603
+ "pageNumber" : page_num,
604
+ "pageSize" : page_size,
605
+ "changeTypeIndex" : "2"
606
+ }
607
+ response_text = fetch(self.web_page, url, payload)
608
+ error_code = response_text.get('code')
609
+ if str(error_code) != '0':
610
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
611
+ list_item = response_text['info']['data']['list']
612
+ total = response_text['info']['data']['count']
613
+ totalPage = math.ceil(total / page_size)
614
+
615
+ if len(list_item_cache) == int(total):
616
+ return list_item_cache
617
+
618
+ for page in range(2, totalPage + 1):
619
+ log(f'获取台账明细列表 第{page}/{totalPage}页')
620
+ payload['pageNumber'] = page
621
+ response_text = fetch(self.web_page, url, payload)
622
+ spu_list_new = response_text['info']['data']['list']
623
+ list_item += spu_list_new
624
+ time.sleep(0.1)
625
+
626
+ for item in list_item:
627
+ supplierSku = item['supplierSku']
628
+ item['store_username'] = self.store_username
629
+ item['store_name'] = self.store_name
630
+ item['store_manager'] = self.config.shein_store_manager.get(str(self.store_username).lower())
631
+ item['cost_price'] = self.bridge.get_sku_cost(supplierSku, self.config.erp_source)
632
+ item['sku_img'] = self.bridge.get_sku_img(supplierSku, self.config.erp_source)
633
+
634
+ write_dict_to_file(cache_file, list_item)
635
+
636
+ return list_item
637
+
591
638
  def get_ledger_list(self, source='mb'):
592
639
  page_num = 1
593
640
  page_size = 200 # 列表最多返回200条数据 大了没有用
@@ -2,6 +2,7 @@ import json
2
2
 
3
3
  from .mysql_module.shein_return_order_model import SheinReturnOrderManager
4
4
  from .mysql_module.shein_product_model import SheinProductManager
5
+ from .mysql_module.shein_ledger_model import SheinLedgerManager
5
6
  from .fun_base import log
6
7
 
7
8
  import os
@@ -10,6 +11,12 @@ class SheinMysql:
10
11
  def __init__(self, config):
11
12
  self.config = config
12
13
 
14
+ def upsert_shein_ledger(self, json_file):
15
+ log(f'当前使用的数据库: {self.config.db.database_url}')
16
+ manager = SheinLedgerManager(self.config.db.database_url)
17
+ manager.create_tables()
18
+ manager.import_from_json_file(json_file)
19
+
13
20
  def upsert_shein_return_order(self, json_file):
14
21
  log(f'当前使用的数据库: {self.config.db.database_url}')
15
22
  # 创建管理器实例
@@ -159,6 +159,34 @@ class TimeUtils:
159
159
 
160
160
  return start_date.strftime(format_str), end_date.strftime(format_str)
161
161
 
162
+ @staticmethod
163
+ def get_month_first_day(start_from: Optional[str] = None, format_str: str = '%Y-%m-%d') -> str:
164
+ """
165
+ 获取某月的第一天
166
+
167
+ Args:
168
+ start_from: 参考日期,格式为'YYYY-MM-DD',默认使用今天
169
+ format_str: 返回的日期格式,默认是'%Y-%m-%d'
170
+
171
+ Returns:
172
+ 指定格式的某月第一天日期字符串
173
+
174
+ Example:
175
+ >>> get_month_first_day('2025-07-02')
176
+ '2025-07-01'
177
+ """
178
+ if start_from:
179
+ try:
180
+ base_date = datetime.strptime(start_from, '%Y-%m-%d')
181
+ except ValueError:
182
+ raise ValueError("start_from 格式必须是 'YYYY-MM-DD'")
183
+ else:
184
+ base_date = datetime.today()
185
+
186
+ # 获取当月第一天
187
+ first_day = base_date.replace(day=1)
188
+ return first_day.strftime(format_str)
189
+
162
190
  @staticmethod
163
191
  def get_past_nth_day(n: int, start_from: Optional[str] = None, format_str: str = '%Y-%m-%d') -> str:
164
192
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.1.13
3
+ Version: 1.1.15
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -29,6 +29,7 @@ qrpa.egg-info/SOURCES.txt
29
29
  qrpa.egg-info/dependency_links.txt
30
30
  qrpa.egg-info/top_level.txt
31
31
  qrpa/mysql_module/__init__.py
32
+ qrpa/mysql_module/shein_ledger_model.py
32
33
  qrpa/mysql_module/shein_product_model.py
33
34
  qrpa/mysql_module/shein_return_order_model.py
34
35
  tests/test_db_migrator.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