qrpa 1.1.37__tar.gz → 1.1.39__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.
- {qrpa-1.1.37 → qrpa-1.1.39}/PKG-INFO +1 -1
- {qrpa-1.1.37 → qrpa-1.1.39}/pyproject.toml +1 -1
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/new_product_analysis_model.py +11 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_excel.py +156 -4
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_lib.py +58 -1
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa.egg-info/PKG-INFO +1 -1
- {qrpa-1.1.37 → qrpa-1.1.39}/README.md +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/RateLimitedSender.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/__init__.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/db_migrator.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/feishu_bot_app.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/feishu_client.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/feishu_logic.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/fun_base.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/fun_excel.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/fun_file.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/fun_web.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/fun_win.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/__init__.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/shein_ledger_model.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/shein_product_model.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/shein_return_order_model.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/mysql_module/shein_store_model.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_daily_report_model.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_mysql.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_sqlite.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/shein_ziniao.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/temu_chrome.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/temu_excel.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/temu_lib.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/time_utils.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/time_utils_example.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa/wxwork.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa.egg-info/SOURCES.txt +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa.egg-info/dependency_links.txt +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/qrpa.egg-info/top_level.txt +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/setup.cfg +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/setup.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/tests/test_db_migrator.py +0 -0
- {qrpa-1.1.37 → qrpa-1.1.39}/tests/test_wxwork.py +0 -0
|
@@ -36,6 +36,13 @@ class SheinNewProductAnalysis(Base):
|
|
|
36
36
|
skc = Column(String(100), nullable=False, comment='平台SKC')
|
|
37
37
|
sku_supplier_no = Column(String(100), nullable=True, comment='商家SKC')
|
|
38
38
|
layer_nm = Column(String(100), nullable=True, comment='商品层级')
|
|
39
|
+
|
|
40
|
+
# 品类信息
|
|
41
|
+
new_cate1_nm = Column(String(200), nullable=True, comment='一级品类名称')
|
|
42
|
+
new_cate2_nm = Column(String(200), nullable=True, comment='二级品类名称')
|
|
43
|
+
new_cate3_nm = Column(String(200), nullable=True, comment='三级品类名称')
|
|
44
|
+
new_cate4_nm = Column(String(200), nullable=True, comment='四级品类名称')
|
|
45
|
+
|
|
39
46
|
goods_name = Column(String(500), nullable=True, comment='商品标题')
|
|
40
47
|
img_url = Column(String(500), nullable=True, comment='SKC图片URL')
|
|
41
48
|
|
|
@@ -339,6 +346,10 @@ class NewProductAnalysisManager:
|
|
|
339
346
|
'skc' : skc,
|
|
340
347
|
'sku_supplier_no' : item.get('skuSupplierNo'),
|
|
341
348
|
'layer_nm' : item.get('layerNm'),
|
|
349
|
+
'new_cate1_nm' : item.get('newCate1Nm'),
|
|
350
|
+
'new_cate2_nm' : item.get('newCate2Nm'),
|
|
351
|
+
'new_cate3_nm' : item.get('newCate3Nm'),
|
|
352
|
+
'new_cate4_nm' : item.get('newCate4Nm'),
|
|
342
353
|
'goods_name' : item.get('goodsName'),
|
|
343
354
|
'img_url' : item.get('imgUrl'),
|
|
344
355
|
'onsale_flag' : int(item.get('onsaleFlag', 0)),
|
|
@@ -5,6 +5,7 @@ from .time_utils import TimeUtils
|
|
|
5
5
|
from .wxwork import WxWorkBot
|
|
6
6
|
from .shein_daily_report_model import SheinStoreSalesDetailManager, SheinStoreSalesDetail
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import pandas as pd
|
|
9
10
|
import numpy as np
|
|
10
11
|
|
|
@@ -2938,7 +2939,7 @@ class SheinExcel:
|
|
|
2938
2939
|
# 在"金额"列后面插入两列
|
|
2939
2940
|
new_header = header[:amount_col_idx + 1] + ['ERP成本', '成本总额'] + header[amount_col_idx + 1:]
|
|
2940
2941
|
log(f'在第{amount_col_idx + 1}列(金额)后面插入"ERP成本"和"成本总额"两列')
|
|
2941
|
-
|
|
2942
|
+
|
|
2942
2943
|
# 业务单号列在插入新列后的索引需要调整
|
|
2943
2944
|
if business_no_col_idx is not None and business_no_col_idx > amount_col_idx:
|
|
2944
2945
|
business_no_col_idx_adjusted = business_no_col_idx + 2 # 因为插入了2列
|
|
@@ -3005,7 +3006,7 @@ class SheinExcel:
|
|
|
3005
3006
|
# 查找需要格式化为文本的列
|
|
3006
3007
|
text_format_columns = []
|
|
3007
3008
|
str_keywords = ['业务单号']
|
|
3008
|
-
|
|
3009
|
+
|
|
3009
3010
|
def col_idx_to_letter(idx):
|
|
3010
3011
|
"""将列索引转换为Excel列字母 (0->A, 1->B, ..., 25->Z, 26->AA, ...)"""
|
|
3011
3012
|
result = ''
|
|
@@ -3015,7 +3016,7 @@ class SheinExcel:
|
|
|
3015
3016
|
result = chr(65 + idx % 26) + result
|
|
3016
3017
|
idx //= 26
|
|
3017
3018
|
return result
|
|
3018
|
-
|
|
3019
|
+
|
|
3019
3020
|
for col_idx, col_name in enumerate(header):
|
|
3020
3021
|
col_name_str = str(col_name)
|
|
3021
3022
|
# 检查列名是否包含需要保持为文本的关键词
|
|
@@ -3023,7 +3024,7 @@ class SheinExcel:
|
|
|
3023
3024
|
col_letter = col_idx_to_letter(col_idx)
|
|
3024
3025
|
text_format_columns.append(col_letter)
|
|
3025
3026
|
log(f'列"{col_name}"(第{col_idx}列,Excel列{col_letter})将格式化为文本')
|
|
3026
|
-
|
|
3027
|
+
|
|
3027
3028
|
log(f'共{len(text_format_columns)}列需要格式化为文本: {text_format_columns}')
|
|
3028
3029
|
|
|
3029
3030
|
# 使用batch_excel_operations批量写入和格式化
|
|
@@ -3123,3 +3124,154 @@ class SheinExcel:
|
|
|
3123
3124
|
add_formula_for_column(sheet, '退货单履约服务费', f'=SUMIFS({detail_sheet}!{amount_col}:{amount_col},{detail_sheet}!{store_col}:{store_col},A2,{detail_sheet}!{type_col}:{type_col},"支出",{detail_sheet}!{bill_type_col}:{bill_type_col},"退货单履约服务费")')
|
|
3124
3125
|
|
|
3125
3126
|
add_formula_for_column(sheet, '利润', '=D2-E2-F2-G2-H2-I2-J2-K2')
|
|
3127
|
+
|
|
3128
|
+
def write_vssv_order_list(self):
|
|
3129
|
+
"""
|
|
3130
|
+
写入VSSV增值服务订单列表到Excel
|
|
3131
|
+
"""
|
|
3132
|
+
# 获取上个月的时间范围
|
|
3133
|
+
first_day, last_day = TimeUtils.get_last_month_range()
|
|
3134
|
+
last_month = TimeUtils.get_last_month()
|
|
3135
|
+
|
|
3136
|
+
# 读取店铺别名映射
|
|
3137
|
+
dict_store = read_dict_from_file(self.config.shein_store_alias)
|
|
3138
|
+
|
|
3139
|
+
# 准备Excel数据
|
|
3140
|
+
header = ['店铺账号', '店铺名称', '增值服务订单号', '增值服务单号', '采购订单号',
|
|
3141
|
+
'平台SKC', '商家SKC', 'SKC数量','扣款单号', '订单状态', '实际总金额',
|
|
3142
|
+
'增值服务项', '创建时间', '完成时间']
|
|
3143
|
+
excel_data = [header]
|
|
3144
|
+
|
|
3145
|
+
# 遍历vssv_order目录下的所有店铺数据
|
|
3146
|
+
src_directory = f'{self.config.auto_dir}/shein/vssv_order'
|
|
3147
|
+
|
|
3148
|
+
if not os.path.exists(src_directory):
|
|
3149
|
+
log(f'VSSV订单目录不存在: {src_directory}')
|
|
3150
|
+
return
|
|
3151
|
+
|
|
3152
|
+
for entry in os.listdir(src_directory):
|
|
3153
|
+
# 检查是否为匹配的缓存文件
|
|
3154
|
+
if entry.startswith(f"vssv_order_list_") and entry.endswith(f"_{first_day}_{last_day}.json"):
|
|
3155
|
+
file_path = os.path.join(src_directory, entry)
|
|
3156
|
+
|
|
3157
|
+
# 从文件名中提取店铺账号
|
|
3158
|
+
# 格式: vssv_order_list_{store_username}_{first_day}_{last_day}.json
|
|
3159
|
+
parts = entry.replace('.json', '').split('_')
|
|
3160
|
+
if len(parts) >= 5:
|
|
3161
|
+
# vssv_order_list_{store_username}_{first_day}_{last_day}
|
|
3162
|
+
# parts[0]='vssv', parts[1]='order', parts[2]='list', parts[3]=store_username
|
|
3163
|
+
store_username = parts[3]
|
|
3164
|
+
else:
|
|
3165
|
+
log(f'无法解析店铺账号: {entry}')
|
|
3166
|
+
continue
|
|
3167
|
+
|
|
3168
|
+
# 获取店铺名称
|
|
3169
|
+
store_name = dict_store.get(store_username, store_username)
|
|
3170
|
+
|
|
3171
|
+
# 读取订单数据
|
|
3172
|
+
order_list = read_dict_from_file(file_path)
|
|
3173
|
+
log(f'读取店铺 {store_name}({store_username}) 的VSSV订单: {len(order_list)}条')
|
|
3174
|
+
|
|
3175
|
+
# 处理每条订单数据
|
|
3176
|
+
for order in order_list:
|
|
3177
|
+
# 基础订单信息
|
|
3178
|
+
order_no = order.get('orderNo', '-')
|
|
3179
|
+
sub_order_no = order.get('subOrderNo', '-')
|
|
3180
|
+
purchase_no = order.get('purchaseNo', '-')
|
|
3181
|
+
skc_img_path = order.get('skcImgPath', '')
|
|
3182
|
+
skc = order.get('skc', '-')
|
|
3183
|
+
supplier_product_number = order.get('supplierProductNumber', '-')
|
|
3184
|
+
skc_num = order.get('skcNum', 0)
|
|
3185
|
+
order_state_name = order.get('orderStateName', '-')
|
|
3186
|
+
actual_total_amount = order.get('actualTotalAmount', 0)
|
|
3187
|
+
|
|
3188
|
+
# 提取扣款单号(从vendorRepairList数组中)
|
|
3189
|
+
vendor_repair_no = '-'
|
|
3190
|
+
vendor_repair_list = order.get('vendorRepairList', [])
|
|
3191
|
+
if vendor_repair_list and len(vendor_repair_list) > 0:
|
|
3192
|
+
vendor_repair_no = vendor_repair_list[0].get('vendorRepairNo', '-')
|
|
3193
|
+
|
|
3194
|
+
# 提取创建时间和完成时间(从orderChangeLogVo中)
|
|
3195
|
+
create_time = '-'
|
|
3196
|
+
finish_time = '-'
|
|
3197
|
+
order_change_log = order.get('orderChangeLogVo', [])
|
|
3198
|
+
for log_item in order_change_log:
|
|
3199
|
+
if log_item.get('operateType') == 12: # 创建时间
|
|
3200
|
+
create_time = log_item.get('operateTime', '-')
|
|
3201
|
+
elif log_item.get('operateType') == 4: # 增值订单完成时间
|
|
3202
|
+
finish_time = log_item.get('operateTime', '-')
|
|
3203
|
+
|
|
3204
|
+
# 获取增值服务项列表并合并成一个字符串
|
|
3205
|
+
service_items = order.get('subOrderServiceItemVoList', [])
|
|
3206
|
+
service_items_text = '-'
|
|
3207
|
+
|
|
3208
|
+
if service_items:
|
|
3209
|
+
# 将所有服务项合并成一个字符串,每个服务项一行
|
|
3210
|
+
service_lines = []
|
|
3211
|
+
for service_item in service_items:
|
|
3212
|
+
service_name = service_item.get('serviceItemName', '-')
|
|
3213
|
+
settlement_qty = service_item.get('settlementQuantity', 0)
|
|
3214
|
+
item_amount = service_item.get('itemTotalAmount', 0)
|
|
3215
|
+
price = service_item.get('price', 0)
|
|
3216
|
+
# 格式:服务项名称 | 数量 | 金额
|
|
3217
|
+
service_line = f"{service_name}: {settlement_qty}x{price}=¥{item_amount}"
|
|
3218
|
+
service_lines.append(service_line)
|
|
3219
|
+
service_items_text = '\n'.join(service_lines)
|
|
3220
|
+
|
|
3221
|
+
# 添加一行数据
|
|
3222
|
+
row_item = []
|
|
3223
|
+
row_item.append(store_username) # 店铺账号
|
|
3224
|
+
row_item.append(store_name) # 店铺名称
|
|
3225
|
+
row_item.append(order_no) # 增值服务订单号
|
|
3226
|
+
row_item.append(sub_order_no) # 增值服务单号
|
|
3227
|
+
row_item.append(purchase_no) # 采购订单号
|
|
3228
|
+
# row_item.append(skc_img_path) # SKC图片
|
|
3229
|
+
row_item.append(skc) # 平台SKC
|
|
3230
|
+
row_item.append(supplier_product_number) # 商家SKC
|
|
3231
|
+
row_item.append(skc_num) # SKC数量
|
|
3232
|
+
row_item.append(vendor_repair_no) # 扣款单号
|
|
3233
|
+
row_item.append(order_state_name) # 订单状态
|
|
3234
|
+
row_item.append(actual_total_amount) # 实际总金额
|
|
3235
|
+
row_item.append(service_items_text) # 增值服务项(合并)
|
|
3236
|
+
row_item.append(create_time) # 创建时间
|
|
3237
|
+
row_item.append(finish_time) # 完成时间
|
|
3238
|
+
excel_data.append(row_item)
|
|
3239
|
+
|
|
3240
|
+
log(f'共收集到 {len(excel_data) - 1} 条VSSV订单数据')
|
|
3241
|
+
|
|
3242
|
+
# 如果没有数据,只有表头,则不生成Excel
|
|
3243
|
+
if len(excel_data) <= 1:
|
|
3244
|
+
log('没有VSSV订单数据,跳过Excel生成')
|
|
3245
|
+
return
|
|
3246
|
+
|
|
3247
|
+
# 写入Excel
|
|
3248
|
+
excel_path = self.config.excel_path
|
|
3249
|
+
sheet_name = f'{last_month}月增值服务列表'
|
|
3250
|
+
|
|
3251
|
+
batch_excel_operations(excel_path, [
|
|
3252
|
+
(sheet_name, 'write', excel_data, ['C', 'D', 'E', 'I']), # 订单号、单号、采购单号、扣款单号格式化为文本
|
|
3253
|
+
(sheet_name, 'format', self.format_vssv_order_list),
|
|
3254
|
+
('Sheet1', 'delete')
|
|
3255
|
+
])
|
|
3256
|
+
|
|
3257
|
+
log(f'VSSV订单列表已写入: {excel_path}')
|
|
3258
|
+
|
|
3259
|
+
def format_vssv_order_list(self, sheet):
|
|
3260
|
+
"""
|
|
3261
|
+
格式化VSSV订单列表Excel
|
|
3262
|
+
"""
|
|
3263
|
+
beautify_title(sheet)
|
|
3264
|
+
add_borders(sheet)
|
|
3265
|
+
format_to_money(sheet, ['金额', '总金额'])
|
|
3266
|
+
column_to_right(sheet, ['金额', '数量', '总金额'])
|
|
3267
|
+
format_to_datetime(sheet, ['时间'])
|
|
3268
|
+
column_to_left(sheet, ['店铺账号', '订单号', '单号', 'SKC', '增值服务项'])
|
|
3269
|
+
wrap_column(sheet, ['增值服务项']) # 增值服务项列自动换行
|
|
3270
|
+
autofit_column(sheet, ['店铺名称', '订单状态'])
|
|
3271
|
+
specify_column_width(sheet, ['增值服务订单号', '增值服务单号', '采购订单号', '扣款单号'], 160 / 6)
|
|
3272
|
+
specify_column_width(sheet, ['增值服务项'], 280 / 6) # 服务项列设置较宽
|
|
3273
|
+
|
|
3274
|
+
# 插入SKC图片
|
|
3275
|
+
# InsertImageV2(sheet, ['SKC图片'], 'shein', 90)
|
|
3276
|
+
|
|
3277
|
+
sheet.autofit()
|
|
@@ -758,6 +758,63 @@ class SheinLib:
|
|
|
758
758
|
log(f'Bridge数据刷新完成', self.store_username, self.store_name)
|
|
759
759
|
return data_list
|
|
760
760
|
|
|
761
|
+
def get_vssv_order_list(self):
|
|
762
|
+
"""
|
|
763
|
+
获取VSSV订单列表
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
web_page: 页面对象
|
|
767
|
+
store_username: 店铺账号
|
|
768
|
+
store_name: 店铺名称
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
list: 订单列表
|
|
772
|
+
"""
|
|
773
|
+
page_num = 1
|
|
774
|
+
page_size = 200
|
|
775
|
+
first_day, last_day = TimeUtils.get_last_month_range()
|
|
776
|
+
|
|
777
|
+
cache_file = f'{self.config.auto_dir}/shein/vssv_order/vssv_order_list_{self.store_username}_{first_day}_{last_day}.json'
|
|
778
|
+
list_item = read_dict_from_file(cache_file, 3600 * 24 * 20)
|
|
779
|
+
if len(list_item) > 0:
|
|
780
|
+
return list_item
|
|
781
|
+
|
|
782
|
+
url = f"https://sso.geiwohuo.com/vssv/order/page"
|
|
783
|
+
payload = {
|
|
784
|
+
"deductionStatus": "2",
|
|
785
|
+
"beginTime" : f"{first_day} 00:00:00",
|
|
786
|
+
"endTime" : f"{last_day} 23:59:59",
|
|
787
|
+
"pageNumber" : page_num,
|
|
788
|
+
"pageSize" : page_size
|
|
789
|
+
}
|
|
790
|
+
response_text = fetch(self.web_page, url, payload)
|
|
791
|
+
error_code = response_text.get('code')
|
|
792
|
+
if str(error_code) != '0':
|
|
793
|
+
raise send_exception(json.dumps(response_text, ensure_ascii=False))
|
|
794
|
+
raise
|
|
795
|
+
list_item = response_text['info']['list']
|
|
796
|
+
total = response_text['info']['count']
|
|
797
|
+
totalPage = math.ceil(total / page_size)
|
|
798
|
+
|
|
799
|
+
for page in range(2, totalPage + 1):
|
|
800
|
+
log(f'获取VSSV订单列表 第{page}/{totalPage}页')
|
|
801
|
+
page_num = page
|
|
802
|
+
payload = {
|
|
803
|
+
"deductionStatus": "2",
|
|
804
|
+
"beginTime" : f"{first_day} 00:00:00",
|
|
805
|
+
"endTime" : f"{last_day} 23:59:59",
|
|
806
|
+
"pageNumber" : page_num,
|
|
807
|
+
"pageSize" : page_size
|
|
808
|
+
}
|
|
809
|
+
response_text = fetch(self.web_page, url, payload)
|
|
810
|
+
spu_list_new = response_text['info']['list']
|
|
811
|
+
list_item += spu_list_new
|
|
812
|
+
time.sleep(0.1)
|
|
813
|
+
|
|
814
|
+
write_dict_to_file(cache_file, list_item)
|
|
815
|
+
|
|
816
|
+
return list_item
|
|
817
|
+
|
|
761
818
|
def get_replenish_list(self):
|
|
762
819
|
page_num = 1
|
|
763
820
|
page_size = 50
|
|
@@ -1225,7 +1282,7 @@ class SheinLib:
|
|
|
1225
1282
|
|
|
1226
1283
|
# 活动信息
|
|
1227
1284
|
# AB实验数据
|
|
1228
|
-
|
|
1285
|
+
|
|
1229
1286
|
# 预先过滤掉不需要的商品状态
|
|
1230
1287
|
log(f'过滤前商品数量: {len(spu_list)}')
|
|
1231
1288
|
exclude_levels = ['退供款', '自主停产', '自主下架']
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|