qrpa 1.1.79__py3-none-any.whl

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/shein_excel.py ADDED
@@ -0,0 +1,3809 @@
1
+ from .fun_excel import *
2
+ from .fun_base import *
3
+ from .fun_file import read_dict_from_file, read_dict_from_file_ex, write_dict_to_file, write_dict_to_file_ex, delete_file
4
+ from .time_utils import TimeUtils
5
+ from .wxwork import WxWorkBot
6
+ from .shein_daily_report_model import SheinStoreSalesDetailManager, SheinStoreSalesDetail
7
+
8
+ import os
9
+ import pandas as pd
10
+ import numpy as np
11
+
12
+ class SheinExcel:
13
+
14
+ def __init__(self, config, bridge=None):
15
+ self.config = config
16
+ self.bridge = bridge
17
+ pass
18
+
19
+ def write_sku_not_found(self):
20
+ cache_file = f'{self.config.auto_dir}/shein/dict/sku_not_found.json'
21
+ dict_sku_not_found = read_dict_from_file(cache_file)
22
+
23
+ excel_data = []
24
+ for store_username, data_list in dict_sku_not_found.items():
25
+ excel_data += data_list
26
+
27
+ sheet_name1 = '未匹配SKU_需运营调整'
28
+ operations = [
29
+ [sheet_name1, 'write', [['店铺账户', '店铺别名', '店长', 'SPU', 'SKC', '商家SKC', '商家SKU', '上架状态', '商品层次', '错误原因']] + excel_data],
30
+ [sheet_name1, 'format', self.format_sku_not_found],
31
+ ['Sheet1', 'delete'],
32
+ ]
33
+ cache_file = f'{self.config.auto_dir}/shein/dict/sku_to_skc.json'
34
+ sku_to_skc = read_dict_from_file(cache_file)
35
+ excel_data = []
36
+ for store_username, data_list in sku_to_skc.items():
37
+ excel_data += data_list
38
+
39
+ sheet_name = 'sku到skc映射'
40
+ operations.append([sheet_name, 'write', [['商家SKU', '商家SKC']] + excel_data])
41
+ operations.append([sheet_name, 'format', self.format_sku_to_skc])
42
+
43
+ operations.append([sheet_name1, 'move', 1])
44
+
45
+ batch_excel_operations(self.config.excel_sku_not_found, operations)
46
+
47
+ def format_sku_not_found(self, sheet):
48
+ beautify_title(sheet)
49
+ add_borders(sheet)
50
+ column_to_left(sheet, ['商家SKC', '商家SKU', '商品层次'])
51
+ pass
52
+
53
+ def format_sku_to_skc(self, sheet):
54
+ beautify_title(sheet)
55
+ add_borders(sheet)
56
+ column_to_left(sheet, ['商家SKC', '商家SKU'])
57
+ pass
58
+
59
+ def get_supplier_name(self, store_username):
60
+ cache_file = f'{self.config.auto_dir}/shein/dict/supplier_data.json'
61
+ info = read_dict_from_file_ex(cache_file, store_username)
62
+ return info['supplier_name']
63
+
64
+ def write_withdraw_report_2024(self, year=2024):
65
+ if year == 2025:
66
+ excel_path = create_file_path(self.config.excel_withdraw_2025)
67
+ else:
68
+ excel_path = create_file_path(self.config.excel_withdraw_2024)
69
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
70
+
71
+ header = ['店铺名称', '店铺账号', '供应商名称', '交易单号', '提现时间', '提现成功时间', '更新时间', '提现明细单号',
72
+ '收款帐户', '收款帐户所在地', '净金额', '保证金', '手续费', '汇率', '收款金额', '提现状态']
73
+ summary_excel_data = [header]
74
+ # 先读取提现明细列表写入
75
+ first_day, last_day = TimeUtils.get_year_range_time(year)
76
+ cache_file = f'{self.config.auto_dir}/shein/cache/withdraw_list_{first_day}_{last_day}.json'
77
+ dict_withdraw = read_dict_from_file(cache_file)
78
+ account_list = []
79
+ for store_username, list_withdraw in dict_withdraw.items():
80
+ store_name = dict_store.get(store_username)
81
+ supplier_name = self.get_supplier_name(store_username)
82
+ for withdraw in list_withdraw:
83
+ row_item = []
84
+ row_item.append(store_name)
85
+ row_item.append(store_username)
86
+ row_item.append(supplier_name)
87
+ row_item.append(withdraw['withdrawNo'])
88
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw['createTime']))
89
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw.get('transferSuccessTime')))
90
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw['lastUpdateTime']))
91
+ row_item.append(withdraw['transferNo'])
92
+ account = withdraw['sourceAccountValue']
93
+ if account not in account_list:
94
+ account_list.append(account)
95
+ row_item.append(account)
96
+ row_item.append(withdraw['accountAreaCode'])
97
+ row_item.append(withdraw['netAmount'])
98
+ row_item.append(withdraw['depositAmount'])
99
+ row_item.append(withdraw['commissionAmount'])
100
+ row_item.append(withdraw['exchangeRate'])
101
+ row_item.append(withdraw['receivingAmount'])
102
+ row_item.append(withdraw['withdrawStatusDesc'])
103
+ summary_excel_data.append(row_item)
104
+ sheet_name = '提现明细汇总'
105
+
106
+ operations = [
107
+ [sheet_name, 'write', summary_excel_data, ],
108
+ [sheet_name, 'format', self.format_withdraw_detail]
109
+ ]
110
+
111
+ header = [
112
+ ['收款账户', '总收款金额'],
113
+ ['汇总', ''],
114
+ ]
115
+ summary_excel_data = header
116
+ for account in account_list:
117
+ row_item = []
118
+ row_item.append(account)
119
+ row_item.append('')
120
+ summary_excel_data.append(row_item)
121
+
122
+ sheet_name = f'汇总{year}'
123
+
124
+ operations.append([sheet_name, 'write', summary_excel_data])
125
+ operations.append([sheet_name, 'format', self.format_withdraw_2024])
126
+ operations.append([sheet_name, 'move', 1])
127
+ operations.append(['Sheet1', 'delete'])
128
+
129
+ batch_excel_operations(excel_path, operations)
130
+
131
+ def format_withdraw_detail(self, sheet):
132
+ beautify_title(sheet)
133
+ column_to_right(sheet, ['金额'])
134
+ format_to_money(sheet, ['金额', '保证金', '手续费'])
135
+ format_to_datetime(sheet, ['时间'])
136
+ add_borders(sheet)
137
+
138
+ def format_withdraw_2024(self, sheet):
139
+ beautify_title(sheet)
140
+ column_to_right(sheet, ['金额'])
141
+ format_to_money(sheet, ['金额'])
142
+ format_to_datetime(sheet, ['时间'])
143
+ add_sum_for_cell(sheet, ['总收款金额'])
144
+ add_formula_for_column(sheet, '总收款金额', "=SUMIFS(提现明细汇总!O:O,提现明细汇总!I:I,A3,提现明细汇总!P:P,\"提现成功\")", 3)
145
+ add_borders(sheet)
146
+
147
+ def write_product(self):
148
+ erp = self.config.erp_source
149
+ excel_path = create_file_path(self.config.excel_shein_skc_profit)
150
+ cache_file = f'{self.config.auto_dir}/shein/product/product_{TimeUtils.today_date()}.json'
151
+ dict_product = read_dict_from_file(cache_file)
152
+
153
+ skc_header = ['SKC', '商家SKC', 'SKC图片', '近7天利润', '近30天利润']
154
+ skc_excel_data = []
155
+ dict_skc = []
156
+
157
+ summary_excel_data = []
158
+ header = []
159
+ for store_username, excel_data in dict_product.items():
160
+ header = excel_data[0]
161
+ new_data = []
162
+ for row_item in excel_data[1:]:
163
+ supplier_sku = row_item[5]
164
+ row_item[10] = self.bridge.get_sku_cost(supplier_sku, erp)
165
+ new_data.append(row_item)
166
+
167
+ if row_item[2] not in dict_skc:
168
+ dict_skc.append(row_item[2])
169
+ stat_data = []
170
+ stat_data.append(row_item[2])
171
+ stat_data.append(row_item[3])
172
+ stat_data.append(row_item[4])
173
+ stat_data.append('')
174
+ stat_data.append('')
175
+ skc_excel_data.append(stat_data)
176
+
177
+ summary_excel_data += new_data
178
+
179
+ sheet_name = '商品库'
180
+
181
+ batch_excel_operations(excel_path, [
182
+ (sheet_name, 'write', [header] + summary_excel_data),
183
+ (sheet_name, 'format', self.format_product),
184
+ ])
185
+
186
+ sheet_name = 'Sheet1'
187
+ profit_data = [skc_header] + skc_excel_data
188
+ batch_excel_operations(excel_path, [
189
+ (sheet_name, 'write', sort_by_column(profit_data, 4, 1)),
190
+ (sheet_name, 'format', self.format_profit),
191
+ (sheet_name, 'format', sort_by_column_excel, 'E'),
192
+ ])
193
+
194
+ def write_product_month_analysis(self):
195
+ """写入月度产品分析数据到Excel"""
196
+ excel_path = create_file_path(self.config.excel_shein_skc_profit)
197
+
198
+ # 读取第一个sheet的数据
199
+ cache_file = f'{self.config.auto_dir}/shein/product_analysis/product_analysis_{TimeUtils.today_date()}.json'
200
+ dict_product = read_dict_from_file(cache_file)
201
+
202
+ summary_excel_data = []
203
+ header = []
204
+ for store_username, excel_data in dict_product.items():
205
+ if not header and excel_data:
206
+ header = excel_data[0]
207
+ # 跳过表头,添加所有数据行
208
+ if len(excel_data) > 1:
209
+ summary_excel_data += excel_data[1:]
210
+
211
+ # 读取第二个sheet的数据(每日趋势)
212
+ cache_file2 = f'{self.config.auto_dir}/shein/product_analysis/product_analysis_2_{TimeUtils.today_date()}.json'
213
+ dict_product2 = read_dict_from_file(cache_file2)
214
+
215
+ summary_excel_data2 = []
216
+ header2 = []
217
+ for store_username, excel_data2 in dict_product2.items():
218
+ if not header2 and excel_data2:
219
+ header2 = excel_data2[0]
220
+ # 跳过表头,添加所有数据行
221
+ if len(excel_data2) > 1:
222
+ summary_excel_data2 += excel_data2[1:]
223
+
224
+ sheet_name = '月度商品分析'
225
+ sheet_name2 = 'SKC每日趋势'
226
+
227
+ # 分开处理两个sheet,避免操作冲突
228
+ # 第一个sheet:月度商品分析
229
+ batch_excel_operations(excel_path, [
230
+ (sheet_name, 'write', [header] + summary_excel_data),
231
+ (sheet_name, 'format', self.format_product_month_analysis),
232
+ (sheet_name, 'move', 1),
233
+ ])
234
+
235
+ # 第二个sheet:SKC每日趋势(独立操作)
236
+ batch_excel_operations(excel_path, [
237
+ (sheet_name2, 'write', [header2] + summary_excel_data2),
238
+ (sheet_name2, 'format', self.format_product_month_analysis_trend),
239
+ ('Sheet1', 'delete'),
240
+ ])
241
+
242
+ def format_product_month_analysis(self, sheet):
243
+ """格式化月度商品分析表格"""
244
+ # 合并相同SKC的单元格
245
+ merge_by_column_v2(sheet, 'skc', ['店铺信息', '商品信息', 'SKC图片', '30天SKC曝光', '30天SKC点击率', '30天SKC转化率', '评论数', '差评率', '客单退货件数'])
246
+
247
+ # 美化表头
248
+ beautify_title(sheet)
249
+
250
+ # 添加边框
251
+ add_borders(sheet)
252
+
253
+ # 设置列格式
254
+ format_to_money(sheet, ['销售额', '核价', '成本', '30天利润'])
255
+ format_to_percent(sheet, ['30天利润率', '30天SKC点击率', '30天SKC转化率', '差评率'])
256
+ column_to_right(sheet, ['销售额', '核价', '成本', '30天利润'])
257
+
258
+ # 设置列宽和对齐
259
+ specify_column_width(sheet, ['店铺信息', '商品信息', 'SKU信息'], 160 / 6)
260
+ column_to_left(sheet, ['店铺信息', '商品信息', 'SKU信息'])
261
+ autofit_column(sheet, ['商品信息', 'SKU信息'])
262
+
263
+ # 添加公式
264
+ # 销售额 (M列) = SKU30天销量 (L列) * 核价 (N列)
265
+ add_formula_for_column(sheet, '销售额', '=IF(AND(ISNUMBER(L2), ISNUMBER(N2)), L2*N2, 0)')
266
+ # 30天利润 (P列) = SKU30天销量 (L列) * (核价 (N列) - 成本 (O列))
267
+ add_formula_for_column(sheet, '30天利润', '=IF(AND(ISNUMBER(L2), ISNUMBER(N2), ISNUMBER(O2)), L2*(N2-O2), 0)')
268
+ # 30天利润率 (Q列) = 30天利润 (P列) / 销售额 (M列)
269
+ add_formula_for_column(sheet, '30天利润率', '=IF(AND(ISNUMBER(M2), M2<>0), P2/M2, 0)')
270
+
271
+ # 插入图片(使用V3一次性插入多列,避免图片被清空)
272
+ InsertImageV3(sheet, ['SKC图片', 'SKU图片'], 'shein', [90, 60])
273
+
274
+ # 按SKC着色(改进版,正确处理合并单元格)
275
+ colorize_by_field_v2(sheet, 'skc')
276
+
277
+ def format_product_month_analysis_trend(self, sheet):
278
+ """格式化SKC每日趋势表格"""
279
+
280
+ # 合并相同SKC的单元格
281
+ merge_by_column_v2(sheet, 'skc', ['店铺信息', '商品信息', 'SKC图片'])
282
+
283
+ # 美化表头
284
+ beautify_title(sheet)
285
+
286
+ # 添加边框
287
+ add_borders(sheet)
288
+
289
+ # 设置列格式
290
+ format_to_date(sheet, ['日期'])
291
+ format_to_percent(sheet, ['SKC点击率', 'SKC转化率'])
292
+
293
+ # 设置列宽和对齐
294
+ specify_column_width(sheet, ['店铺信息', '商品信息'], 160 / 6)
295
+ column_to_left(sheet, ['店铺信息', '商品信息'])
296
+ autofit_column(sheet, ['商品信息'])
297
+
298
+ # 插入图片
299
+ InsertImageV2(sheet, ['SKC图片'], 'shein', 90)
300
+
301
+ # 按SKC着色(改进版,正确处理合并单元格)
302
+ colorize_by_field_v2(sheet, 'skc')
303
+
304
+ def format_profit(self, sheet):
305
+ beautify_title(sheet)
306
+ add_borders(sheet)
307
+ format_to_money(sheet, ['成本价', '核价', '利润'])
308
+ column_to_right(sheet, ['成本价', '核价', '利润'])
309
+ add_formula_for_column(sheet, '近7天利润', '=SUMIFS(商品库!L:L,商品库!P:P,A2)')
310
+ add_formula_for_column(sheet, '近30天利润', '=SUMIFS(商品库!M:M,商品库!P:P,A2)')
311
+ InsertImageV2(sheet, ['SKC图片'], 'shein', 90)
312
+
313
+ def format_product(self, sheet):
314
+ merge_by_column_v2(sheet, 'SPU', ['店铺信息', '产品信息'])
315
+ merge_by_column_v2(sheet, 'SKC', ['SKC图片', '商家SKC'])
316
+ beautify_title(sheet)
317
+ add_borders(sheet)
318
+ format_to_datetime(sheet, ['时间'])
319
+ format_to_money(sheet, ['成本价', '核价', '利润'])
320
+ column_to_right(sheet, ['成本价', '核价', '利润'])
321
+ autofit_column(sheet, ['产品信息'])
322
+ column_to_left(sheet, ['产品信息', '商家SKU', '商家SKC', '属性集'])
323
+ specify_column_width(sheet, ['店铺信息', '产品信息', '属性集'], 160 / 6)
324
+ specify_column_width(sheet, ['商家SKU', '商家SKC'], 220 / 6)
325
+ add_formula_for_column(sheet, '近7天利润', '=IF(ISNUMBER(K2), H2*(J2-K2),0)')
326
+ add_formula_for_column(sheet, '近30天利润', '=IF(ISNUMBER(K2), I2*(J2-K2),0)')
327
+ InsertImageV2(sheet, ['SKC图片'], 'shein', 150, '商家SKC', 'shein_skc_img')
328
+
329
+ def write_week_ntb(self):
330
+ excel_path = create_file_path(self.config.excel_week_report)
331
+
332
+ cache_file = f'{self.config.auto_dir}/shein/dict/new_product_to_bak_{TimeUtils.today_date()}.json'
333
+ dict = read_dict_from_file(cache_file)
334
+ # dict_store = read_dict_from_file(config.dict_store_cache)
335
+
336
+ summary_excel_data = []
337
+ header = []
338
+ dict_store_bak_stat = {}
339
+ for store_username, excel_data in dict.items():
340
+ # store_name = dict_store.get(store_username)
341
+ if dict_store_bak_stat.get(store_username) is None:
342
+ dict_store_bak_stat[store_username] = [0, 0]
343
+ for item in excel_data[1:]:
344
+ dict_store_bak_stat[store_username][0] += 1
345
+ if int(item[6]) == 1:
346
+ dict_store_bak_stat[store_username][1] += 1
347
+ header = excel_data[0]
348
+ summary_excel_data += excel_data[1:]
349
+ summary_excel_data = [header] + summary_excel_data
350
+ log(summary_excel_data)
351
+ sheet_name = '新品转备货款明细'
352
+
353
+ # write_data(excel_path, sheet_name, summary_excel_data)
354
+ # self.format_week_ntb(excel_path, sheet_name)
355
+
356
+ batch_excel_operations(excel_path, [
357
+ (sheet_name, 'write', summary_excel_data),
358
+ (sheet_name, 'format', self.format_week_ntb),
359
+ ])
360
+
361
+ dict_key = f'{self.config.auto_dir}/shein/dict/dict_store_bak_stat_{TimeUtils.today_date()}.json'
362
+ write_dict_to_file(dict_key, dict_store_bak_stat)
363
+
364
+ def format_week_ntb(self, sheet):
365
+ beautify_title(sheet)
366
+ format_to_date(sheet, ['统计日期'])
367
+ format_to_percent(sheet, ['占比'])
368
+ colorize_by_field(sheet, 'SPU')
369
+ column_to_left(sheet, ['商品信息', '第4周SKC点击率/SKC转化率', '第4周SKC销量/SKC曝光'])
370
+ autofit_column(sheet, ['店铺名称', '商品信息', '第4周SKC点击率/SKC转化率', '第4周SKC销量/SKC曝光'])
371
+ add_borders(sheet)
372
+ InsertImageV2(sheet, ['SKC图片'], 'shein', 120)
373
+
374
+ def dealFundsExcelFormat(self, sheet):
375
+ col_a = find_column_by_data(sheet, 1, '店铺名称')
376
+ col_b = find_column_by_data(sheet, 1, '在途商品金额')
377
+ col_c = find_column_by_data(sheet, 1, '在仓商品金额')
378
+ col_d = find_column_by_data(sheet, 1, '待结算金额')
379
+ col_e = find_column_by_data(sheet, 1, '可提现金额')
380
+ col_f = find_column_by_data(sheet, 1, '汇总')
381
+ col_g = find_column_by_data(sheet, 1, '导出时间')
382
+ col_h = find_column_by_data(sheet, 1, '销售出库金额')
383
+
384
+ sheet.range(f'{col_a}:{col_a}').column_width = 25
385
+ sheet.range(f'{col_g}:{col_g}').number_format = 'yyyy-mm-dd hh:mm:ss'
386
+
387
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
388
+ sheet.range(f'{col_b}2').formula = f'=SUM({col_b}3:{col_b}{last_row})'
389
+ sheet.range(f'{col_b}2').number_format = '¥#,##0.00'
390
+ cell = sheet.range(f'{col_b}2')
391
+ cell.api.Font.Color = 255 # RGB(255, 0, 0),红色对应的颜色代码
392
+ cell.api.Font.Bold = True
393
+
394
+ sheet.range(f'{col_c}2').formula = f'=SUM({col_c}3:{col_c}{last_row})'
395
+ sheet.range(f'{col_c}2').number_format = '¥#,##0.00'
396
+ cell = sheet.range(f'{col_c}2')
397
+ cell.api.Font.Color = 255 # RGB(255, 0, 0),红色对应的颜色代码
398
+ cell.api.Font.Bold = True
399
+
400
+ sheet.range(f'{col_d}2').formula = f'=SUM({col_d}3:{col_d}{last_row})'
401
+ sheet.range(f'{col_d}2').number_format = '¥#,##0.00'
402
+ cell = sheet.range(f'{col_d}2')
403
+ cell.api.Font.Color = 255 # RGB(255, 0, 0),红色对应的颜色代码
404
+ cell.api.Font.Bold = True
405
+
406
+ sheet.range(f'{col_e}2').formula = f'=SUM({col_e}3:{col_e}{last_row})'
407
+ sheet.range(f'{col_e}2').number_format = '¥#,##0.00'
408
+ cell = sheet.range(f'{col_e}2')
409
+ cell.api.Font.Color = 255 # RGB(255, 0, 0),红色对应的颜色代码
410
+ cell.api.Font.Bold = True
411
+
412
+ sheet.range(f'{col_h}2').formula = f'=SUM({col_h}3:{col_h}{last_row})'
413
+ sheet.range(f'{col_h}2').number_format = '¥#,##0.00'
414
+ cell = sheet.range(f'{col_h}2')
415
+ cell.api.Font.Color = 255 # RGB(255, 0, 0),红色对应的颜色代码
416
+ cell.api.Font.Bold = True
417
+
418
+ # 遍历可用行
419
+ used_range_row = sheet.range('A1').expand('down')
420
+ for i, cell in enumerate(used_range_row):
421
+ row = i + 1
422
+ if row < 2:
423
+ continue
424
+ # 设置数字格式
425
+ sheet.range(f'{col_b}{row}').number_format = '¥#,##0.00'
426
+ sheet.range(f'{col_c}{row}').number_format = '¥#,##0.00'
427
+ sheet.range(f'{col_d}{row}').number_format = '¥#,##0.00'
428
+ sheet.range(f'{col_e}{row}').number_format = '¥#,##0.00'
429
+ sheet.range(f'{col_f}{row}').formula = f'=SUM({col_b}{row}:{col_e}{row})'
430
+ sheet.range(f'{col_f}{row}').number_format = '¥#,##0.00'
431
+ sheet.range(f'{col_f}{row}').api.Font.Color = 255
432
+ sheet.range(f'{col_f}{row}').api.Font.Bold = True
433
+
434
+ add_borders(sheet)
435
+
436
+ def save_sheet_img(self, excel_path, sheet_name, columns_to_remove, screenshot_filename=None):
437
+ """
438
+ 对指定sheet进行截图(在副本Excel上操作,不影响原始文件)
439
+ :param excel_path: 原始Excel文件路径
440
+ :param sheet_name: sheet名称
441
+ :param columns_to_remove: 要删除的列名列表
442
+ :param screenshot_filename: 截图文件名(不含路径),如果为None则自动生成
443
+ :return: 截图文件路径
444
+ """
445
+ temp_excel_path = None
446
+ try:
447
+ log(f'开始对"{sheet_name}"进行截图处理,原文件: {excel_path}')
448
+
449
+ # 创建临时副本Excel文件
450
+ import shutil
451
+ screenshot_dir = f'{self.config.auto_dir}/image'
452
+ if not os.path.exists(screenshot_dir):
453
+ os.makedirs(screenshot_dir)
454
+
455
+ temp_excel_path = os.path.join(screenshot_dir, f'_temp_{datetime.now().strftime("%Y%m%d%H%M%S")}.xlsx')
456
+ shutil.copy2(excel_path, temp_excel_path)
457
+ log(f'已创建副本Excel: {temp_excel_path}')
458
+
459
+ # 在副本上进行截图操作
460
+ app, wb, sheet = open_excel(temp_excel_path, sheet_name)
461
+ screenshot_path = self.screenshot_sheet_without_columns(sheet, columns_to_remove, screenshot_filename)
462
+ close_excel(app, wb)
463
+
464
+ # 删除临时副本文件
465
+ if temp_excel_path and os.path.exists(temp_excel_path):
466
+ os.remove(temp_excel_path)
467
+ log('已删除临时副本Excel')
468
+
469
+ log(f'截图处理完成,原文件未被修改')
470
+ return screenshot_path
471
+
472
+ except Exception as e:
473
+ log(f'save_sheet_img失败: {str(e)}')
474
+ import traceback
475
+ log(traceback.format_exc())
476
+ # 清理临时文件
477
+ try:
478
+ if temp_excel_path and os.path.exists(temp_excel_path):
479
+ os.remove(temp_excel_path)
480
+ except:
481
+ pass
482
+ return None
483
+
484
+ def screenshot_sheet_without_columns(self, sheet, columns_to_remove, screenshot_filename):
485
+ """
486
+ 对sheet进行截图,截图前删除指定列(备份数据后删除,截图后恢复)
487
+ :param sheet: xlwings sheet对象
488
+ :param columns_to_remove: 要删除的列名列表
489
+ :param screenshot_filename: 截图文件名(不含路径),如果为None则自动生成
490
+ :return: 截图文件路径
491
+ """
492
+ columns_backup = {} # 备份删除的列数据
493
+ try:
494
+ log(f'开始对sheet "{sheet.name}" 进行截图处理,删除列: {columns_to_remove}')
495
+
496
+ # 获取标题行并备份要删除的列数据
497
+ header_row = sheet.range('1:1').value
498
+ used_range = sheet.used_range
499
+ last_row = used_range.last_cell.row
500
+
501
+ for i, header in enumerate(header_row, start=1):
502
+ if header in columns_to_remove:
503
+ col_letter = xw.utils.col_name(i)
504
+ col_data = sheet.range(f'{col_letter}1:{col_letter}{last_row}').value
505
+ columns_backup[i] = (col_letter, col_data, header)
506
+ log(f'已备份"{header}"列数据,位置: {col_letter},共 {last_row} 行')
507
+
508
+ if not columns_backup:
509
+ log(f'未找到要删除的列: {columns_to_remove},跳过截图处理')
510
+ return None
511
+
512
+ # 删除列
513
+ remove_excel_columns(sheet, columns_to_remove)
514
+ log(f'已删除列: {columns_to_remove}')
515
+
516
+ # 刷新工作表,确保删除操作生效
517
+ sheet.book.app.api.CalculateFull()
518
+ import time
519
+ time.sleep(0.5)
520
+
521
+ # 准备截图路径
522
+ screenshot_dir = f'{self.config.auto_dir}/image'
523
+ if not os.path.exists(screenshot_dir):
524
+ os.makedirs(screenshot_dir)
525
+
526
+ if screenshot_filename is None:
527
+ screenshot_filename = f'shein_财务周报_{datetime.now().strftime("%Y%m%d")}.png'
528
+
529
+ screenshot_path = os.path.join(screenshot_dir, screenshot_filename)
530
+
531
+ # 截图:使用PIL从剪贴板(在保存前截图,避免对象失效)
532
+ screenshot_success = False
533
+ try:
534
+ used_range = sheet.used_range
535
+ # 检查 used_range 是否有效
536
+ if used_range is None or used_range.count == 0:
537
+ log('截图失败:删除列后工作表没有有效数据区域')
538
+ else:
539
+ log(f'截图区域: {used_range.address}, 行数: {used_range.last_cell.row}, 列数: {used_range.last_cell.column}')
540
+ # 如果只有一列或一行,可能需要特殊处理
541
+ if used_range.last_cell.row <= 1 or used_range.last_cell.column < 1:
542
+ log('截图失败:数据区域太小,无法截图')
543
+ else:
544
+ used_range.api.CopyPicture(Appearance=1, Format=2)
545
+ log('已复制为图片到剪贴板')
546
+
547
+ from PIL import ImageGrab
548
+ import time
549
+ time.sleep(1) # 等待剪贴板更新
550
+
551
+ img = ImageGrab.grabclipboard()
552
+ if img:
553
+ img.save(screenshot_path)
554
+ log(f'截图已保存到: {screenshot_path}')
555
+ screenshot_success = True
556
+ else:
557
+ log('截图失败:剪贴板中没有图片')
558
+ except Exception as e:
559
+ log(f'截图失败: {str(e)}')
560
+ import traceback
561
+ log(traceback.format_exc())
562
+
563
+ # 恢复列数据(按从右到左的顺序恢复,避免索引变化)
564
+ log('开始恢复删除的列数据...')
565
+ for col_idx in sorted(columns_backup.keys(), reverse=True):
566
+ col_letter, col_data, col_name = columns_backup[col_idx]
567
+ # 在原位置插入新列
568
+ sheet.range(f'{col_letter}:{col_letter}').api.Insert()
569
+ # 恢复数据 - 将一维列表转换为列格式(二维列表)
570
+ if isinstance(col_data, list):
571
+ # 将 [a, b, c] 转换为 [[a], [b], [c]] 以便按列填充
572
+ col_data_2d = [[item] for item in col_data]
573
+ data_length = len(col_data)
574
+ else:
575
+ # 单个值
576
+ col_data_2d = [[col_data]]
577
+ data_length = 1
578
+ sheet.range(f'{col_letter}1:{col_letter}{data_length}').value = col_data_2d
579
+ log(f'已恢复"{col_name}"列数据到 {col_letter}')
580
+
581
+ log('截图处理完成')
582
+ return screenshot_path if screenshot_success else None
583
+
584
+ except Exception as e:
585
+ log(f'截图处理失败: {str(e)}')
586
+ import traceback
587
+ log(traceback.format_exc())
588
+ # 尝试恢复列数据
589
+ try:
590
+ if columns_backup:
591
+ log('尝试恢复列数据...')
592
+ for col_idx in sorted(columns_backup.keys(), reverse=True):
593
+ col_letter, col_data, col_name = columns_backup[col_idx]
594
+ sheet.range(f'{col_letter}:{col_letter}').api.Insert()
595
+ # 将一维列表转换为列格式
596
+ if isinstance(col_data, list):
597
+ col_data_2d = [[item] for item in col_data]
598
+ data_length = len(col_data)
599
+ else:
600
+ col_data_2d = [[col_data]]
601
+ data_length = 1
602
+ sheet.range(f'{col_letter}1:{col_letter}{data_length}').value = col_data_2d
603
+ log('已恢复列数据')
604
+ except:
605
+ pass
606
+ return None
607
+
608
+ def write_week_finance_report(self):
609
+ cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_lz_{TimeUtils.today_date()}.json'
610
+ dict = read_dict_from_file(cache_file)
611
+ dict_key = f'{self.config.auto_dir}/shein/dict/dict_store_bak_stat_{TimeUtils.today_date()}.json'
612
+ dict_store_bak_stat = read_dict_from_file(dict_key)
613
+ data = []
614
+ for key, val in dict.items():
615
+ data.append(val)
616
+ log(data)
617
+ for item in data:
618
+ store_username = item[1]
619
+ item[11] = dict_store_bak_stat[store_username][0]
620
+ item[12] = dict_store_bak_stat[store_username][1]
621
+
622
+ data.sort(key=lambda row: row[10], reverse=True)
623
+ excel_path = create_file_path(self.config.excel_week_report)
624
+ sheet_name = '按店铺汇总'
625
+
626
+ date_A = f'新品上架数量\n({TimeUtils.get_past_nth_day(29, TimeUtils.get_month_first_day())},{TimeUtils.get_past_nth_day(29, TimeUtils.get_yesterday())})'
627
+ date_B = f'成功转备货款\n({TimeUtils.get_month_first_day()},{TimeUtils.get_yesterday()})'
628
+
629
+ data.insert(0, ['汇总', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''])
630
+ data.insert(0, ['店铺名称', '店铺账号', '店长', '在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '不可提现金额', '保证金', '汇总',
631
+ '销售出库金额', date_A, date_B, '成功率', '导出时间', '店铺ID', '商家ID', '全球唯一编码'])
632
+ write_data(excel_path, sheet_name, data, ['店铺ID', '商家ID'])
633
+ app, wb, sheet = open_excel(excel_path, sheet_name)
634
+ beautify_title(sheet)
635
+
636
+ self.dealFundsExcelFormat(sheet)
637
+ format_to_percent(sheet, ['成功率'], 0)
638
+ add_formula_for_column(sheet, '成功率', '=IF(L2=0, 0, M2/L2)', 2)
639
+ add_formula_for_column(sheet, date_A, "=COUNTIF('新品转备货款明细'!A:A, B3)", 3)
640
+ add_formula_for_column(sheet, date_B, "=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)", 3)
641
+ add_sum_for_cell(sheet, ['不可提现金额', '保证金', date_A, date_B])
642
+ column_to_right(sheet, ['金额', '汇总', '保证金'])
643
+ sheet.autofit()
644
+ autofit_column(sheet, [date_A, date_B])
645
+ delete_sheet_if_exists(wb, 'Sheet1')
646
+ wb.save()
647
+ close_excel(app, wb)
648
+
649
+ new_data = data
650
+ new_data = aggregate_by_column_v2(new_data, '店长', as_str_columns=['店铺ID', '商家ID', '全球唯一编码'])
651
+ new_data_sorted = new_data[1:]
652
+ new_data_sorted.sort(key=lambda row: row[10], reverse=True)
653
+
654
+ sheet_name = '按店长汇总'
655
+ write_data(excel_path, sheet_name, data[:2] + new_data_sorted)
656
+ app, wb, sheet = open_excel(excel_path, sheet_name)
657
+ add_borders(sheet)
658
+ format_to_money(sheet, ['金额', '成本', '保证金'])
659
+ format_to_datetime(sheet, ['时间'])
660
+ format_to_percent(sheet, ['成功率'], 0)
661
+ add_formula_for_column(sheet, '成功率', '=IF(L2=0, 0, M2/L2)', 2)
662
+ # 聚合的不能使用这种公式
663
+ # add_formula_for_column(sheet, '新品上架数量',"=COUNTIF('新品转备货款明细'!A:A, B3)",3)
664
+ # add_formula_for_column(sheet, '成功转备货款',"=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)",3)
665
+ add_sum_for_cell(sheet, ['在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '不可提现金额', '保证金', '汇总', '销售出库金额',
666
+ date_A, date_B])
667
+ clear_for_cell(sheet, ['店铺账号', '导出时间', '店铺ID', '商家ID', '全球唯一编码'])
668
+ add_formula_for_column(sheet, '汇总', f'=SUM(D3:I3)', 3)
669
+ set_title_style(sheet)
670
+ column_to_right(sheet, ['金额', '汇总', '保证金'])
671
+ # sheet.autofit()
672
+ autofit_column(sheet, ['店铺名称', '店铺账号', date_A, date_B, '导出时间'])
673
+ wb.save()
674
+ close_excel(app, wb)
675
+
676
+ self.save_sheet_img(excel_path, '按店长汇总', ['在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '不可提现金额', '保证金', '汇总'])
677
+
678
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
679
+
680
+ def write_return_list_range(self, erp, start_date, end_date):
681
+ # 更新表头: 移除包裹名、包裹号, 新增退货单号_SKC列
682
+ header = ['退货单号', '退货出库时间', '签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '商家SKU', '属性集', 'SKU退货数量', '平台SKU', 'ERP默认供货商', 'ERP成本', '退货计划单号', '订单号', '发货单', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址', '退货单号_SKC']
683
+ excel_data = [header]
684
+
685
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
686
+
687
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
688
+ dict = read_dict_from_file(cache_file)
689
+ for store_username, shein_back_list in dict.items():
690
+ for item in shein_back_list:
691
+ store_name = dict_store.get(store_username)
692
+
693
+ # 使用 return_goods_detail 替代 return_box_detail
694
+ return_goods_detail = item.get('return_goods_detail', [])
695
+ if len(return_goods_detail) == 0:
696
+ continue
697
+
698
+ returnOrderNo = item['returnOrderNo']
699
+ returnOrderTypeName = item['returnOrderTypeName']
700
+ returnOrderStatusName = item['returnOrderStatusName']
701
+ returnReasonTypeName = item['returnReasonTypeName']
702
+ returnReason = item['returnReason']
703
+ waitReturnQuantity = item['waitReturnQuantity']
704
+ skcReturnQuantity = item['returnQuantity']
705
+ returnAmount = item['returnAmount']
706
+ currencyCode = item['currencyCode']
707
+ returnPlanNo = item['returnPlanNo']
708
+ sellerOrderNo = item['sellerOrderNo']
709
+ sellerDeliveryNo = item['sellerDeliveryNo']
710
+ completeTime = item['completeTime']
711
+ returnWayTypeName = item['returnWayTypeName']
712
+ returnExpressCompanyName = item['returnExpressCompanyName']
713
+ expressNoList = item['expressNoList']
714
+ returnAddress = item['returnAddress']
715
+ sellerContract = item['sellerContract']
716
+ sellerContractPhone = item['sellerContractPhone']
717
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
718
+
719
+ # 处理入库问题图片地址(转换为超链接格式)
720
+ if item['returnScrapType'] == 1:
721
+ urls = item.get('qc_report_url', '-')
722
+ else:
723
+ # 多个图片URL用换行分隔,Excel会自动识别为超链接
724
+ urls = '\n'.join(item['rejectPicUrlList']) if item.get('rejectPicUrlList') else '-'
725
+
726
+ # 遍历商品明细(return_goods_detail结构)
727
+ for goods_item in return_goods_detail:
728
+ skc_img = goods_item.get('imgPath', '')
729
+ skc = goods_item.get('skc', '')
730
+ supplierCode = goods_item.get('supplierCode', '')
731
+
732
+ # 遍历SKU明细
733
+ for sku_item in goods_item.get('details', []):
734
+ platformSku = sku_item.get('platformSku', '')
735
+ supplierSku = sku_item.get('supplierSku', '')
736
+ suffixZh = sku_item.get('suffixZh', '')
737
+ returnQuantity = sku_item.get('returnQuantity', 0)
738
+
739
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
740
+ skc_info = f'供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
741
+
742
+ row_item = []
743
+ row_item.append(returnOrderNo)
744
+ row_item.append(completeTime)
745
+ row_item.append(isSign)
746
+ row_item.append(store_info)
747
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
748
+ row_item.append(returnReasonTypeName)
749
+ row_item.append(returnReason)
750
+ row_item.append(skc_img)
751
+ row_item.append(skc_info)
752
+ row_item.append(supplierSku)
753
+ row_item.append(suffixZh)
754
+ row_item.append(returnQuantity)
755
+ row_item.append(platformSku)
756
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
757
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
758
+ # 移除了包裹名和包裹号字段
759
+ row_item.append(returnPlanNo)
760
+ row_item.append(sellerOrderNo)
761
+ row_item.append(sellerDeliveryNo)
762
+ row_item.append(returnWayTypeName)
763
+ row_item.append(returnExpressCompanyName)
764
+ row_item.append(expressNoList)
765
+ row_item.append(returnAddress)
766
+ row_item.append(sellerContract)
767
+ row_item.append(sellerContractPhone)
768
+ row_item.append(urls)
769
+ row_item.append(f'{returnOrderNo}_{skc}') # 退货单号_SKC列,用于合并单元格
770
+
771
+ excel_data.append(row_item)
772
+
773
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
774
+ write_dict_to_file(cache_file_excel, excel_data)
775
+
776
+ sheet_name = '希音退货列表'
777
+ batch_excel_operations(self.config.excel_return_list, [
778
+ (sheet_name, 'write', excel_data, ['Y']), # 更新超链接列为Y列(入库问题图片地址)
779
+ (sheet_name, 'format', self.format_return_list)
780
+ ])
781
+
782
+ # 退货列表
783
+ def write_return_list(self, erp, start_date, end_date):
784
+ # 更新表头: 移除包裹名、包裹号, 新增退货单号_SKC列
785
+ header = ['退货单号', '退货出库时间', '签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '商家SKU', '属性集', 'SKU退货数量', '平台SKU', 'ERP默认供货商', 'ERP成本', '退货计划单号', '订单号', '发货单', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址', '退货单号_SKC']
786
+ excel_data = [header]
787
+
788
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
789
+
790
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
791
+ dict = read_dict_from_file(cache_file)
792
+ for store_username, shein_back_list in dict.items():
793
+ for item in shein_back_list:
794
+ store_name = dict_store.get(store_username)
795
+
796
+ # 使用 return_goods_detail 替代 return_box_detail
797
+ return_goods_detail = item.get('return_goods_detail', [])
798
+ if len(return_goods_detail) == 0:
799
+ continue
800
+
801
+ returnOrderNo = item['returnOrderNo']
802
+ returnOrderTypeName = item['returnOrderTypeName']
803
+ returnOrderStatusName = item['returnOrderStatusName']
804
+ returnReasonTypeName = item['returnReasonTypeName']
805
+ returnReason = item['returnReason']
806
+ waitReturnQuantity = item['waitReturnQuantity']
807
+ skcReturnQuantity = item['returnQuantity']
808
+ returnAmount = item['returnAmount']
809
+ currencyCode = item['currencyCode']
810
+ returnPlanNo = item['returnPlanNo']
811
+ sellerOrderNo = item['sellerOrderNo']
812
+ sellerDeliveryNo = item['sellerDeliveryNo']
813
+ completeTime = item['completeTime']
814
+ returnWayTypeName = item['returnWayTypeName']
815
+ returnExpressCompanyName = item['returnExpressCompanyName']
816
+ expressNoList = item['expressNoList']
817
+ returnAddress = item['returnAddress']
818
+ sellerContract = item['sellerContract']
819
+ sellerContractPhone = item['sellerContractPhone']
820
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
821
+
822
+ # 处理入库问题图片地址(转换为超链接格式)
823
+ if item['returnScrapType'] == 1:
824
+ urls = item.get('qc_report_url', '-')
825
+ else:
826
+ # 多个图片URL用换行分隔,Excel会自动识别为超链接
827
+ urls = '\n'.join(item['rejectPicUrlList']) if item.get('rejectPicUrlList') else '-'
828
+
829
+ # 遍历商品明细(return_goods_detail结构)
830
+ for goods_item in return_goods_detail:
831
+ skc_img = goods_item.get('imgPath', '')
832
+ skc = goods_item.get('skc', '')
833
+ supplierCode = goods_item.get('supplierCode', '')
834
+
835
+ # 遍历SKU明细
836
+ for sku_item in goods_item.get('details', []):
837
+ platformSku = sku_item.get('platformSku', '')
838
+ supplierSku = sku_item.get('supplierSku', '')
839
+ suffixZh = sku_item.get('suffixZh', '')
840
+ returnQuantity = sku_item.get('returnQuantity', 0)
841
+
842
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
843
+ skc_info = f'供方货号: {supplierCode}'
844
+
845
+ row_item = []
846
+ row_item.append(returnOrderNo)
847
+ row_item.append(completeTime)
848
+ row_item.append(isSign)
849
+ row_item.append(store_info)
850
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
851
+ row_item.append(returnReasonTypeName)
852
+ row_item.append(returnReason)
853
+ row_item.append(skc_img)
854
+ row_item.append(skc_info)
855
+ row_item.append(supplierSku)
856
+ row_item.append(suffixZh)
857
+ row_item.append(returnQuantity)
858
+ row_item.append(platformSku)
859
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
860
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
861
+ # 移除了包裹名和包裹号字段
862
+ row_item.append(returnPlanNo)
863
+ row_item.append(sellerOrderNo)
864
+ row_item.append(sellerDeliveryNo)
865
+ row_item.append(returnWayTypeName)
866
+ row_item.append(returnExpressCompanyName)
867
+ row_item.append(expressNoList)
868
+ row_item.append(returnAddress)
869
+ row_item.append(sellerContract)
870
+ row_item.append(sellerContractPhone)
871
+ row_item.append(urls)
872
+ row_item.append(f'{returnOrderNo}_{skc}') # 退货单号_SKC列,用于合并单元格
873
+
874
+ excel_data.append(row_item)
875
+
876
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
877
+ write_dict_to_file(cache_file_excel, excel_data)
878
+
879
+ # sheet_name = '希音退货列表'
880
+ # batch_excel_operations(self.config.excel_return_list, [
881
+ # (sheet_name, 'write', excel_data, ['Y']), # 更新超链接列为Y列(入库问题图片地址)
882
+ # (sheet_name, 'format', self.format_return_list)
883
+ # ])
884
+
885
+ excel_data = [header]
886
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
887
+ dict = read_dict_from_file(cache_file)
888
+ for store_username, shein_back_list in dict.items():
889
+ for item in shein_back_list:
890
+ store_name = dict_store.get(store_username)
891
+
892
+ # 使用 return_goods_detail 替代 return_box_detail
893
+ return_goods_detail = item.get('return_goods_detail', [])
894
+ if len(return_goods_detail) == 0:
895
+ continue
896
+
897
+ returnOrderNo = item['returnOrderNo']
898
+ returnOrderTypeName = item['returnOrderTypeName']
899
+ returnOrderStatusName = item['returnOrderStatusName']
900
+ returnReasonTypeName = item['returnReasonTypeName']
901
+ returnReason = item['returnReason']
902
+ waitReturnQuantity = item['waitReturnQuantity']
903
+ skcReturnQuantity = item['returnQuantity']
904
+ returnAmount = item['returnAmount']
905
+ currencyCode = item['currencyCode']
906
+ returnPlanNo = item['returnPlanNo']
907
+ sellerOrderNo = item['sellerOrderNo']
908
+ sellerDeliveryNo = item['sellerDeliveryNo']
909
+ completeTime = item['completeTime']
910
+ returnWayTypeName = item['returnWayTypeName']
911
+ returnExpressCompanyName = item['returnExpressCompanyName']
912
+ expressNoList = item['expressNoList']
913
+ returnAddress = item['returnAddress']
914
+ sellerContract = item['sellerContract']
915
+ sellerContractPhone = item['sellerContractPhone']
916
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
917
+
918
+ # 处理入库问题图片地址(转换为超链接格式)
919
+ if item['returnScrapType'] == 1:
920
+ urls = item.get('qc_report_url', '-')
921
+ else:
922
+ # 多个图片URL用换行分隔,Excel会自动识别为超链接
923
+ urls = '\n'.join(item['rejectPicUrlList']) if item.get('rejectPicUrlList') else '-'
924
+
925
+ # 遍历商品明细(return_goods_detail结构)
926
+ for goods_item in return_goods_detail:
927
+ skc_img = goods_item.get('imgPath', '')
928
+ skc = goods_item.get('skc', '')
929
+ supplierCode = goods_item.get('supplierCode', '')
930
+
931
+ # 遍历SKU明细
932
+ for sku_item in goods_item.get('details', []):
933
+ platformSku = sku_item.get('platformSku', '')
934
+ supplierSku = sku_item.get('supplierSku', '')
935
+ suffixZh = sku_item.get('suffixZh', '')
936
+ returnQuantity = sku_item.get('returnQuantity', 0)
937
+
938
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
939
+ skc_info = f'供方货号: {supplierCode}'
940
+
941
+ row_item = []
942
+ row_item.append(returnOrderNo)
943
+ row_item.append(completeTime)
944
+ row_item.append(isSign)
945
+ row_item.append(store_info)
946
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
947
+ row_item.append(returnReasonTypeName)
948
+ row_item.append(returnReason)
949
+ row_item.append(skc_img)
950
+ row_item.append(skc_info)
951
+ row_item.append(supplierSku)
952
+ row_item.append(suffixZh)
953
+ row_item.append(returnQuantity)
954
+ row_item.append(platformSku)
955
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
956
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
957
+ # 移除了包裹名和包裹号字段
958
+ row_item.append(returnPlanNo)
959
+ row_item.append(sellerOrderNo)
960
+ row_item.append(sellerDeliveryNo)
961
+ row_item.append(returnWayTypeName)
962
+ row_item.append(returnExpressCompanyName)
963
+ row_item.append(expressNoList)
964
+ row_item.append(returnAddress)
965
+ row_item.append(sellerContract)
966
+ row_item.append(sellerContractPhone)
967
+ row_item.append(urls)
968
+ row_item.append(f'{returnOrderNo}_{skc}') # 退货单号_SKC列,用于合并单元格
969
+
970
+ excel_data.append(row_item)
971
+
972
+ sheet_name = '昨日退货列表'
973
+
974
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{TimeUtils.today_date()}.json'
975
+ write_dict_to_file(cache_file_excel, excel_data)
976
+
977
+ batch_excel_operations(self.config.excel_return_list, [
978
+ (sheet_name, 'write', excel_data, ['U']),
979
+ (sheet_name, 'format', self.format_return_list),
980
+ ('Sheet1', 'delete')
981
+ ])
982
+
983
+ def format_return_list(self, sheet):
984
+ # 移除了包裹号的合并,因为已经没有包裹名和包裹号字段
985
+ merge_by_column_v2(sheet, '退货单号', ['签收状态', '店铺信息', '店长', '退货类型', '退货原因', '退货计划单号', '订单号', '发货单', '退货出库时间', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址'])
986
+
987
+ # 根据"退货单号_SKC"列合并SKC图片和SKC信息
988
+ merge_by_column_v2(sheet, '退货单号_SKC', ['SKC图片', 'SKC信息'])
989
+
990
+ beautify_title(sheet)
991
+ add_borders(sheet)
992
+ format_to_datetime(sheet, ['时间'])
993
+ format_to_money(sheet, ['单价', '金额', '成本'])
994
+ column_to_right(sheet, ['单价', '金额', '成本'])
995
+ wrap_column(sheet, ['退货原因', '退货地址', '入库问题图片地址'])
996
+ autofit_column(sheet, ['店铺信息', '店铺别名', 'SKC信息'])
997
+ column_to_left(sheet, ['店铺信息', '商家SKU', '供方货号', '属性集', 'SKC信息', '退货地址'])
998
+ specify_column_width(sheet, ['退货原因', 'SKC信息', '商家SKU', '退货地址'], 200 / 6)
999
+ InsertImageV2(sheet, ['SKC图片'])
1000
+
1001
+ def dealReturn(self, sheet):
1002
+ # 遍历可用行
1003
+ used_range_row = sheet.range('A1').expand('down')
1004
+ last_row = len(used_range_row)
1005
+
1006
+ col_0 = find_column_by_data(sheet, 1, '实际退货/报废总数')
1007
+ if last_row < 3:
1008
+ fm = f'=SUM({col_0}3:{col_0}3)'
1009
+ else:
1010
+ fm = f'=SUM({col_0}3:{col_0}{last_row})'
1011
+
1012
+ sheet.range(f'{col_0}2').formula = fm
1013
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1014
+
1015
+ for i, cell in enumerate(used_range_row):
1016
+ row = i + 1
1017
+ if row < 3:
1018
+ continue
1019
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1020
+ sheet.range(f'{row}:{row}').font.size = 11
1021
+
1022
+ used_range_col = sheet.range('A1').expand('right')
1023
+ for j, cell in enumerate(used_range_col):
1024
+ col = j + 1
1025
+ col_name = index_to_column_name(col)
1026
+ col_val = sheet.range(f'{col_name}1').value
1027
+ if col_val not in ['']:
1028
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1029
+
1030
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1031
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1032
+
1033
+ if '时间' in col_val:
1034
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1035
+
1036
+ if '月份' == col_val:
1037
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
1038
+
1039
+ # # 设置标题栏字体颜色与背景色
1040
+ # sheet.range(f'{col_name}1').color = (252,228,214)
1041
+ # sheet.range(f'{col_name}1').font.size = 12
1042
+ # sheet.range(f'{col_name}1').font.bold = True
1043
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
1044
+
1045
+ # 所有列水平居中和垂直居中
1046
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1047
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1048
+
1049
+ # 水平对齐:
1050
+ # -4108:居中
1051
+ # -4131:左对齐
1052
+ # -4152:右对齐
1053
+
1054
+ # 垂直对齐:
1055
+ # -4108:居中
1056
+ # -4160:顶部对齐
1057
+ # -4107:底部对齐
1058
+
1059
+ add_borders(sheet)
1060
+
1061
+ # 获取第一行和第二行
1062
+ rows = sheet.range('1:2')
1063
+ # 设置字体名称
1064
+ rows.font.name = '微软雅黑'
1065
+ # 设置字体大小
1066
+ rows.font.size = 11
1067
+ # 设置字体加粗
1068
+ rows.font.bold = True
1069
+ # 设置标题栏字体颜色与背景色
1070
+ rows.color = (252, 228, 214)
1071
+ # 设置行高
1072
+ rows.row_height = 30
1073
+
1074
+ def dealReplenish(self, sheet):
1075
+ # 遍历可用行
1076
+ used_range_row = sheet.range('A1').expand('down')
1077
+ last_row = len(used_range_row)
1078
+ # 获取最后一行的索引
1079
+ last_col = index_to_column_name(sheet.range('A1').end('right').column)
1080
+ # last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1081
+
1082
+ col_3 = find_column_by_data(sheet, 1, '总金额')
1083
+ if last_row < 3:
1084
+ fm = f'=SUM({col_3}3:{col_3}3)'
1085
+ else:
1086
+ fm = f'=SUM({col_3}3:{col_3}{last_row})'
1087
+
1088
+ sheet.range(f'{col_3}2').formula = fm
1089
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
1090
+
1091
+ for i, cell in enumerate(used_range_row):
1092
+ row = i + 1
1093
+ if row < 3:
1094
+ continue
1095
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1096
+ sheet.range(f'{row}:{row}').font.size = 11
1097
+
1098
+ used_range_col = sheet.range('A1').expand('right')
1099
+ for j, cell in enumerate(used_range_col):
1100
+ col = j + 1
1101
+ col_name = index_to_column_name(col)
1102
+ col_val = sheet.range(f'{col_name}1').value
1103
+ if col_val not in ['']:
1104
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1105
+
1106
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1107
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1108
+
1109
+ if '时间' in col_val:
1110
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1111
+
1112
+ if '月份' == col_val:
1113
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
1114
+
1115
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1116
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1117
+ # 所有列水平居中和垂直居中
1118
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1119
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1120
+
1121
+ add_borders(sheet)
1122
+
1123
+ # === 批量字体设置 ===
1124
+ if last_row > 3:
1125
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1126
+ data_range.api.Font.Name = "Calibri"
1127
+ data_range.api.Font.Size = 11
1128
+
1129
+ # 获取第一行和第二行
1130
+ rows = sheet.range('1:2')
1131
+ # 设置字体名称
1132
+ rows.font.name = '微软雅黑'
1133
+ # 设置字体大小
1134
+ rows.font.size = 11
1135
+ # 设置字体加粗
1136
+ rows.font.bold = True
1137
+ # 设置标题栏字体颜色与背景色
1138
+ rows.color = (252, 228, 214)
1139
+ # 设置行高
1140
+ rows.row_height = 30
1141
+
1142
+ def dealSheinStock(self, sheet):
1143
+ col_0 = find_column_by_data(sheet, 1, '期末库存数量')
1144
+ col_1 = find_column_by_data(sheet, 1, '期末库存金额')
1145
+ col_2 = find_column_by_data(sheet, 1, '单价成本')
1146
+ col_3 = find_column_by_data(sheet, 1, '希音仓成本总额')
1147
+
1148
+ col_4 = find_column_by_data(sheet, 1, '期初库存数量')
1149
+ col_5 = find_column_by_data(sheet, 1, '期初库存金额')
1150
+
1151
+ col_6 = find_column_by_data(sheet, 1, '入库数量')
1152
+ col_7 = find_column_by_data(sheet, 1, '入库金额')
1153
+ col_8 = find_column_by_data(sheet, 1, '出库数量')
1154
+ col_9 = find_column_by_data(sheet, 1, '出库金额')
1155
+
1156
+ col_10 = find_column_by_data(sheet, 1, '出库成本总额')
1157
+ col_11 = find_column_by_data(sheet, 1, '出库利润')
1158
+ col_12 = find_column_by_data(sheet, 1, '出库利润率')
1159
+
1160
+ # 遍历可用行
1161
+ used_range_row = sheet.range('A1').expand('down')
1162
+ last_row = len(used_range_row)
1163
+ # # 获取最后一行的索引
1164
+ last_col = index_to_column_name(sheet.range('A1').end('right').column)
1165
+ # last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1166
+ if last_row > 2:
1167
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1168
+ sheet.range(f'{col_0}2').font.color = (225, 0, 0)
1169
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1170
+ sheet.range(f'{col_1}2').font.color = (225, 0, 0)
1171
+ sheet.range(f'{col_3}2').formula = f'=SUM({col_3}3:{col_3}{last_row})'
1172
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
1173
+
1174
+ sheet.range(f'{col_4}2').formula = f'=SUM({col_4}3:{col_4}{last_row})'
1175
+ sheet.range(f'{col_4}2').font.color = (225, 0, 0)
1176
+ sheet.range(f'{col_5}2').formula = f'=SUM({col_5}3:{col_5}{last_row})'
1177
+ sheet.range(f'{col_5}2').font.color = (225, 0, 0)
1178
+
1179
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
1180
+ sheet.range(f'{col_6}2').font.color = (225, 0, 0)
1181
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
1182
+ sheet.range(f'{col_7}2').font.color = (225, 0, 0)
1183
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1184
+ sheet.range(f'{col_8}2').font.color = (225, 0, 0)
1185
+ sheet.range(f'{col_9}2').formula = f'=SUM({col_9}3:{col_9}{last_row})'
1186
+ sheet.range(f'{col_9}2').font.color = (225, 0, 0)
1187
+
1188
+ sheet.range(f'{col_10}2').formula = f'=SUM({col_10}3:{col_10}{last_row})'
1189
+ sheet.range(f'{col_10}2').font.color = (225, 0, 0)
1190
+
1191
+ sheet.range(f'{col_11}2').formula = f'=SUM({col_11}3:{col_11}{last_row})'
1192
+ sheet.range(f'{col_11}2').font.color = (225, 0, 0)
1193
+
1194
+ if last_row > 3:
1195
+ # 设置毛利润和毛利润率列公式与格式
1196
+ sheet.range(f'{col_3}3').formula = f'={col_0}3*{col_2}3'
1197
+ # AutoFill 快速填充到所有行(3 到 last_row)
1198
+ sheet.range(f'{col_3}3').api.AutoFill(sheet.range(f'{col_3}3:{col_3}{last_row}').api)
1199
+
1200
+ sheet.range(f'{col_10}3').formula = f'={col_8}3*{col_2}3'
1201
+ sheet.range(f'{col_10}3').api.AutoFill(sheet.range(f'{col_10}3:{col_10}{last_row}').api)
1202
+
1203
+ sheet.range(f'{col_11}3').formula = f'={col_9}3-{col_10}3'
1204
+ sheet.range(f'{col_11}3').api.AutoFill(sheet.range(f'{col_11}3:{col_11}{last_row}').api)
1205
+
1206
+ sheet.range(f'{col_12}3').number_format = '0.00%'
1207
+ sheet.range(f'{col_12}3').formula = f'=IF({col_9}3 > 0,{col_11}3/{col_9}3,0)'
1208
+ sheet.range(f'{col_12}3').api.AutoFill(sheet.range(f'{col_12}3:{col_12}{last_row}').api)
1209
+
1210
+ used_range_col = sheet.range('A1').expand('right')
1211
+ for j, cell in enumerate(used_range_col):
1212
+ col = j + 1
1213
+ col_name = index_to_column_name(col)
1214
+ col_val = sheet.range(f'{col_name}1').value
1215
+ if col_val not in ['']:
1216
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1217
+
1218
+ if col_val in ['业务单号']:
1219
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1220
+
1221
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or ('利润' in col_val and '率' not in col_val):
1222
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1223
+
1224
+ if '时间' in col_val:
1225
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1226
+
1227
+ if '月份' == col_val:
1228
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
1229
+
1230
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1231
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1232
+ # 所有列水平居中和垂直居中
1233
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1234
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1235
+
1236
+ add_borders(sheet)
1237
+
1238
+ # === 批量字体设置 ===
1239
+ if last_row > 3:
1240
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1241
+ data_range.api.Font.Name = "Calibri"
1242
+ data_range.api.Font.Size = 11
1243
+
1244
+ set_title_style(sheet)
1245
+
1246
+ def dealSalesPercentageExcel(self, sheet):
1247
+ col_0 = find_column_by_data(sheet, 1, '商家SKU')
1248
+ col_1 = find_column_by_data(sheet, 1, '售出数量')
1249
+ col_2 = find_column_by_data(sheet, 1, '销量占比')
1250
+ col_3 = find_column_by_data(sheet, 1, '售出金额')
1251
+ col_4 = find_column_by_data(sheet, 1, '销售额占比')
1252
+ col_5 = find_column_by_data(sheet, 1, '利润')
1253
+ col_6 = find_column_by_data(sheet, 1, '利润占比')
1254
+ col_7 = find_column_by_data(sheet, 1, 'SKU图片')
1255
+
1256
+ # 遍历可用行
1257
+ used_range_row = sheet.range('B1').expand('down')
1258
+ last_row = len(used_range_row)
1259
+ if last_row > 2:
1260
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1261
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1262
+ sheet.range(f'{col_3}2').formula = f'=SUM({col_3}3:{col_3}{last_row})'
1263
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
1264
+ sheet.range(f'{col_5}2').formula = f'=SUM({col_5}3:{col_5}{last_row})'
1265
+ sheet.range(f'{col_5}2').font.color = (255, 0, 0)
1266
+ # sheet.range(f'{col_7}1:{col_7}2').merge()
1267
+
1268
+ for i, cell in enumerate(used_range_row):
1269
+ row = i + 1
1270
+ if row < 3:
1271
+ continue
1272
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1273
+ sheet.range(f'{row}:{row}').font.size = 11
1274
+
1275
+ sheet.range(f'{col_2}{row}').formula = f'={col_1}{row}/{col_1}2'
1276
+ sheet.range(f'{col_4}{row}').formula = f'={col_3}{row}/{col_3}2'
1277
+ sheet.range(f'{col_6}{row}').formula = f'={col_5}{row}/{col_5}2'
1278
+
1279
+ used_range_col = sheet.range('A1').expand('right')
1280
+ for j, cell in enumerate(used_range_col):
1281
+ col = j + 1
1282
+ col_name = index_to_column_name(col)
1283
+ col_val = sheet.range(f'{col_name}1').value
1284
+ if col_val not in ['']:
1285
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1286
+
1287
+ if col_val in ['占比']:
1288
+ sheet.range(f'{col_name}:{col_name}').number_format = '0.00%'
1289
+
1290
+ if ('价' in col_val or '成本' in col_val or '金额' in col_val or '利润' == col_val):
1291
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1292
+
1293
+ if '时间' in col_val:
1294
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1295
+
1296
+ # # 设置标题栏字体颜色与背景色
1297
+ # sheet.range(f'{col_name}1').color = (252,228,214)
1298
+ # sheet.range(f'{col_name}1').font.size = 12
1299
+ # sheet.range(f'{col_name}1').font.bold = True
1300
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
1301
+
1302
+ # 所有列水平居中和垂直居中
1303
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1304
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1305
+
1306
+ # 水平对齐:
1307
+ # -4108:居中
1308
+ # -4131:左对齐
1309
+ # -4152:右对齐
1310
+
1311
+ # 垂直对齐:
1312
+ # -4108:居中
1313
+ # -4160:顶部对齐
1314
+ # -4107:底部对齐
1315
+
1316
+ add_borders(sheet)
1317
+
1318
+ # 获取第一行和第二行
1319
+ rows = sheet.range('1:2')
1320
+ # 设置字体名称
1321
+ rows.font.name = '微软雅黑'
1322
+ # 设置字体大小
1323
+ rows.font.size = 11
1324
+ # 设置字体加粗
1325
+ rows.font.bold = True
1326
+ # 设置标题栏字体颜色与背景色
1327
+ rows.color = (252, 228, 214)
1328
+ # 设置行高
1329
+ rows.row_height = 30
1330
+
1331
+ def dealMonthNoSettleMentExcel(self, sheet):
1332
+ col_0 = find_column_by_data(sheet, 1, '数量')
1333
+ col_2 = find_column_by_data(sheet, 1, '金额')
1334
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1335
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1336
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1337
+ col_8 = find_column_by_data(sheet, 1, '成本总额')
1338
+
1339
+ # 设置格式
1340
+ used_range_col = sheet.range('A1').expand('right')
1341
+ for j, cell in enumerate(used_range_col):
1342
+ col = j + 1
1343
+ col_name = index_to_column_name(col)
1344
+ col_val = sheet.range(f'{col_name}1').value
1345
+ if col_val not in ['']:
1346
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1347
+
1348
+ if col_val in ['业务单号']:
1349
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1350
+
1351
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1352
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1353
+
1354
+ if '时间' in col_val:
1355
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1356
+
1357
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1358
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1359
+ # 所有列水平居中和垂直居中
1360
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1361
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1362
+
1363
+ # 批量设置公式
1364
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1365
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1366
+ if last_row > 2:
1367
+ # 第3行公式(填一次)
1368
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1369
+ sheet.range(f'{col_8}2').font.color = (255, 0, 0)
1370
+ # AutoFill 快速填充到所有行(3 到 last_row)
1371
+ sheet.range(f'{col_8}3').formula = f'={col_3}3*{col_0}3'
1372
+
1373
+ if last_row > 3:
1374
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1375
+
1376
+ sheet.range(f'{col_4}1').column_width = 0
1377
+
1378
+ # 批量设置边框
1379
+ add_borders(sheet)
1380
+
1381
+ if last_row > 2:
1382
+ # === 批量字体设置 ===
1383
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1384
+ data_range.api.Font.Name = "Calibri"
1385
+ data_range.api.Font.Size = 11
1386
+
1387
+ set_title_style(sheet)
1388
+
1389
+ def dealMonthBackDetailExcel(self, sheet, summary=0):
1390
+ col_0 = find_column_by_data(sheet, 1, '数量')
1391
+ col_2 = find_column_by_data(sheet, 1, '金额')
1392
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1393
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1394
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1395
+ col_8 = find_column_by_data(sheet, 1, '成本总额')
1396
+
1397
+ # 设置格式
1398
+ used_range_col = sheet.range('A1').expand('right')
1399
+ for j, cell in enumerate(used_range_col):
1400
+ col = j + 1
1401
+ col_name = index_to_column_name(col)
1402
+ col_val = sheet.range(f'{col_name}1').value
1403
+ if col_val not in ['']:
1404
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1405
+
1406
+ if col_val in ['业务单号']:
1407
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1408
+
1409
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1410
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1411
+
1412
+ if '时间' in col_val:
1413
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1414
+
1415
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1416
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1417
+ # 所有列水平居中和垂直居中
1418
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1419
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1420
+
1421
+ # 批量设置公式
1422
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1423
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1424
+ if summary == 1:
1425
+ if last_row > 1:
1426
+ sheet.range(f'{col_8}2').formula = f'={col_3}2*{col_0}2'
1427
+ if last_row > 2:
1428
+ # AutoFill 快速填充到所有行(3 到 last_row)
1429
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1430
+ else:
1431
+ if last_row > 2:
1432
+ # 合计行设置
1433
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1434
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1435
+
1436
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1437
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1438
+
1439
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1440
+ sheet.range(f'{col_8}2').font.color = (255, 0, 0)
1441
+
1442
+ # AutoFill 快速填充到所有行(3 到 last_row)
1443
+ sheet.range(f'{col_8}3').formula = f'={col_3}3*{col_0}3'
1444
+
1445
+ if last_row > 3:
1446
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1447
+
1448
+ set_title_style(sheet)
1449
+
1450
+ sheet.range(f'{col_4}1').column_width = 0
1451
+
1452
+ # 批量设置边框
1453
+ add_borders(sheet)
1454
+
1455
+ if last_row > 3:
1456
+ # === 批量字体设置 ===
1457
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1458
+ data_range.api.Font.Name = "Calibri"
1459
+ data_range.api.Font.Size = 11
1460
+
1461
+ def dealMonthSalesDetailExcel(self, sheet):
1462
+ col_0 = find_column_by_data(sheet, 1, '数量')
1463
+ col_1 = find_column_by_data(sheet, 1, '利润')
1464
+ col_2 = find_column_by_data(sheet, 1, '金额')
1465
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1466
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1467
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1468
+ col_6 = find_column_by_data(sheet, 1, '售出数量')
1469
+ col_7 = find_column_by_data(sheet, 1, '售出金额')
1470
+
1471
+ # 设置格式
1472
+ used_range_col = sheet.range('A1').expand('right')
1473
+ for j, cell in enumerate(used_range_col):
1474
+ col = j + 1
1475
+ col_name = index_to_column_name(col)
1476
+ col_val = sheet.range(f'{col_name}1').value
1477
+ if col_val not in ['']:
1478
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1479
+
1480
+ if col_val in ['业务单号']:
1481
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1482
+
1483
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1484
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1485
+
1486
+ if '时间' in col_val:
1487
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1488
+
1489
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1490
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1491
+ # 所有列水平居中和垂直居中
1492
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1493
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1494
+
1495
+ # 批量设置公式
1496
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1497
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1498
+ if last_row > 2:
1499
+ # 第3行公式(填一次)
1500
+ sheet.range(
1501
+ f'{col_1}3').formula = f'=IF(AND(ISNUMBER({col_3}3),{col_5}3="收入结算"),{col_2}3-{col_3}3*{col_0}3,0)'
1502
+ sheet.range(f'{col_6}3').formula = f'=IF(AND(ISNUMBER({col_3}3),{col_5}3="收入结算"),{col_0}3,0)'
1503
+ sheet.range(f'{col_7}3').formula = f'=IF(AND(ISNUMBER({col_3}3),{col_5}3="收入结算"),{col_2}3,0)'
1504
+
1505
+ if last_row > 3:
1506
+ # AutoFill 快速填充到所有行(3 到 last_row)
1507
+ sheet.range(f'{col_1}3').api.AutoFill(sheet.range(f'{col_1}3:{col_1}{last_row}').api)
1508
+ sheet.range(f'{col_6}3').api.AutoFill(sheet.range(f'{col_6}3:{col_6}{last_row}').api)
1509
+ sheet.range(f'{col_7}3').api.AutoFill(sheet.range(f'{col_7}3:{col_7}{last_row}').api)
1510
+
1511
+ # 合计行设置
1512
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1513
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1514
+
1515
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1516
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1517
+
1518
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1519
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1520
+
1521
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
1522
+ sheet.range(f'{col_6}2').font.color = (255, 0, 0)
1523
+
1524
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
1525
+ sheet.range(f'{col_7}2').font.color = (255, 0, 0)
1526
+
1527
+ sheet.range(f'{col_4}1').column_width = 0
1528
+
1529
+ # 批量设置边框
1530
+ add_borders(sheet)
1531
+
1532
+ if last_row > 3:
1533
+ # === 批量字体设置 ===
1534
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1535
+ data_range.api.Font.Name = "Calibri"
1536
+ data_range.api.Font.Size = 11
1537
+ log(f'设置字体: A3:{col_7}{last_row}')
1538
+
1539
+ # 获取第一行和第二行
1540
+ rows = sheet.range('1:2')
1541
+ # 设置字体名称
1542
+ rows.font.name = '微软雅黑'
1543
+ # 设置字体大小
1544
+ rows.font.size = 11
1545
+ # 设置字体加粗
1546
+ rows.font.bold = True
1547
+ # 设置标题栏字体颜色与背景色
1548
+ rows.color = (252, 228, 214)
1549
+ # 设置行高
1550
+ rows.row_height = 30
1551
+
1552
+ def calc_month_sales_percentage(self, month_data):
1553
+ df = pd.DataFrame(data=month_data[2:], columns=month_data[:1][0])
1554
+
1555
+ # 确保 "商家SKU" 是字符串
1556
+ df["商家SKU"] = df["商家SKU"].astype(str).str.strip()
1557
+
1558
+ # 确保 "数量", "金额", "单价成本" 是数值类型
1559
+ df["售出数量"] = pd.to_numeric(df["售出数量"], errors="coerce")
1560
+ df["售出金额"] = pd.to_numeric(df["售出金额"], errors="coerce")
1561
+ df["单价成本"] = pd.to_numeric(df["单价成本"], errors="coerce")
1562
+
1563
+ # 重新计算利润
1564
+ df["利润"] = np.where(
1565
+ df["结算类型"] == "收入结算",
1566
+ df["售出金额"] - (df["单价成本"] * df["售出数量"]),
1567
+ 0
1568
+ )
1569
+
1570
+ # 进行分组统计(求和)
1571
+ summary = df.groupby("商家SKU", as_index=False).agg({
1572
+ "售出数量": "sum",
1573
+ "售出金额": "sum",
1574
+ "利润" : "sum",
1575
+ "SKU图片" : "first"
1576
+ })
1577
+
1578
+ # 计算总值
1579
+ total_quantity = summary["售出数量"].sum()
1580
+ total_amount = summary["售出金额"].sum()
1581
+ total_profit = summary["利润"].sum()
1582
+
1583
+ # 计算占比
1584
+ summary["销量占比"] = summary["售出数量"] / total_quantity * 100
1585
+ summary["销售额占比"] = summary["售出金额"] / total_amount * 100
1586
+ summary["利润占比"] = summary["利润"] / total_profit * 100
1587
+
1588
+ # 确保显示 2 位小数,并加上百分号
1589
+ summary["销量占比"] = summary["销量占比"].map(lambda x: f"{x:.2f}%")
1590
+ summary["销售额占比"] = summary["销售额占比"].map(lambda x: f"{x:.2f}%")
1591
+ summary["利润占比"] = summary["利润占比"].map(lambda x: f"{x:.2f}%")
1592
+
1593
+ # 重新排序列
1594
+ summary = summary[["SKU图片", "商家SKU", "售出数量", "销量占比", "售出金额", "销售额占比", "利润", "利润占比"]]
1595
+ summary_list = summary.values.tolist()
1596
+
1597
+ summary_list.insert(0, ['', '合计', '', '', '', '', '', '']) # 把表头插入到数据列表的第一行
1598
+ # 添加标题行(表头)
1599
+ header = summary.columns.tolist()
1600
+ summary_list.insert(0, header) # 把表头插入到数据列表的第一行
1601
+
1602
+ return summary_list
1603
+
1604
+ def dealMonthSalesDetailExcel_old(self, sheet):
1605
+ col_0 = find_column_by_data(sheet, 1, '数量')
1606
+ col_1 = find_column_by_data(sheet, 1, '利润')
1607
+ col_2 = find_column_by_data(sheet, 1, '金额')
1608
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1609
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1610
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1611
+ col_6 = find_column_by_data(sheet, 1, '售出数量')
1612
+ col_7 = find_column_by_data(sheet, 1, '售出金额')
1613
+ # 遍历可用行
1614
+ used_range_row = sheet.range('A1').expand('down')
1615
+ last_row = len(used_range_row)
1616
+ for i, cell in enumerate(used_range_row):
1617
+ row = i + 1
1618
+ if row < 3:
1619
+ continue
1620
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1621
+ sheet.range(f'{row}:{row}').font.size = 11
1622
+ range0 = f'{col_0}{row}'
1623
+ range2 = f'{col_2}{row}'
1624
+ range3 = f'{col_3}{row}'
1625
+ range5 = f'{col_5}{row}'
1626
+ # 设置毛利润和毛利润率列公式与格式
1627
+ sheet.range(
1628
+ f'{col_1}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range2}-{range3}*{range0},0)'
1629
+ sheet.range(f'{col_6}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range0},0)'
1630
+ sheet.range(f'{col_7}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range2},0)'
1631
+ log(f'处理公式: {row}/{last_row}')
1632
+
1633
+ if last_row > 2:
1634
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1635
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1636
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1637
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1638
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1639
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1640
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
1641
+ sheet.range(f'{col_6}2').font.color = (255, 0, 0)
1642
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
1643
+ sheet.range(f'{col_7}2').font.color = (255, 0, 0)
1644
+ sheet.range(f'{col_4}1').column_width = 0
1645
+ # # 设置计算模式为自动计算
1646
+ # sheet.api.Application.Calculation = -4105 # -4105 代表自动计算模式
1647
+ # # 手动触发一次计算
1648
+ # sheet.api.Calculate()
1649
+
1650
+ used_range_col = sheet.range('A1').expand('right')
1651
+ for j, cell in enumerate(used_range_col):
1652
+ col = j + 1
1653
+ col_name = index_to_column_name(col)
1654
+ col_val = sheet.range(f'{col_name}1').value
1655
+ if col_val not in ['']:
1656
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1657
+
1658
+ if col_val in ['业务单号']:
1659
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1660
+
1661
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1662
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1663
+
1664
+ if '时间' in col_val:
1665
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1666
+
1667
+ # # 设置标题栏字体颜色与背景色
1668
+ # sheet.range(f'{col_name}1').color = (252,228,214)
1669
+ # sheet.range(f'{col_name}1').font.size = 12
1670
+ # sheet.range(f'{col_name}1').font.bold = True
1671
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
1672
+
1673
+ # 所有列水平居中和垂直居中
1674
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1675
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1676
+
1677
+ # 水平对齐:
1678
+ # -4108:居中
1679
+ # -4131:左对齐
1680
+ # -4152:右对齐
1681
+
1682
+ # 垂直对齐:
1683
+ # -4108:居中
1684
+ # -4160:顶部对齐
1685
+ # -4107:底部对齐
1686
+
1687
+ add_borders(sheet)
1688
+
1689
+ # 获取第一行和第二行
1690
+ rows = sheet.range('1:2')
1691
+ # 设置字体名称
1692
+ rows.font.name = '微软雅黑'
1693
+ # 设置字体大小
1694
+ rows.font.size = 11
1695
+ # 设置字体加粗
1696
+ rows.font.bold = True
1697
+ # 设置标题栏字体颜色与背景色
1698
+ rows.color = (252, 228, 214)
1699
+ # 设置行高
1700
+ rows.row_height = 30
1701
+
1702
+ def write_month_sales_detail(self, store_username, store_name, ledger_list, shein_stock_list, shein_replenish_list, shein_return_list, shein_back_list, shein_no_settlement_list):
1703
+ last_month = TimeUtils.get_last_month()
1704
+
1705
+ supplierName = ''
1706
+
1707
+ excel_path_month = str(self.config.excel_shein_finance_month_report).replace('#store_name#', store_name)
1708
+
1709
+ month_data = [[
1710
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '利润', '售出数量', '售出金额', '售出成本', '添加时间',
1711
+ '业务单号', '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', 'SKU图片',
1712
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']]
1713
+ log('len(ledger_list)', len(ledger_list))
1714
+ for month_item in ledger_list:
1715
+ row_item = []
1716
+ supplierName = month_item['supplierName']
1717
+ platform_sku = month_item['sku']
1718
+ row_item.append(platform_sku)
1719
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1720
+ row_item.append(supplier_sku)
1721
+ row_item.append(month_item['suffixZh'])
1722
+ row_item.append(month_item['quantity'])
1723
+ row_item.append(month_item['cost'])
1724
+ row_item.append(month_item['amount'])
1725
+ row_item.append(month_item['cost_price'])
1726
+ row_item.append('')
1727
+ row_item.append(
1728
+ month_item['quantity'] if month_item['cost_price'] and month_item['settleTypeName'] == '收入结算' else 0)
1729
+ row_item.append(
1730
+ month_item['amount'] if month_item['cost_price'] and month_item['settleTypeName'] == '收入结算' else 0)
1731
+ row_item.append('')
1732
+ row_item.append(month_item['addTime'])
1733
+ row_item.append(month_item['businessNo'])
1734
+ row_item.append(month_item['billNo'])
1735
+ row_item.append(month_item['displayChangeTypeName'])
1736
+ row_item.append(month_item['settleTypeName'])
1737
+ row_item.append(month_item['skc'])
1738
+ row_item.append(month_item['supplierCode'])
1739
+ row_item.append(month_item['supplierName'])
1740
+ row_item.append(month_item['sku_img'])
1741
+ month_data.append(row_item)
1742
+
1743
+ sheet_name = f'{last_month}月销售明细'
1744
+
1745
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 1, 2, False), ['L'])
1746
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1747
+ set_title_style(sheet, 2)
1748
+ set_body_style(sheet, 3)
1749
+ add_borders(sheet)
1750
+ format_to_money(sheet, ['单价', '金额', '利润'])
1751
+ format_to_datetime(sheet, ['时间'])
1752
+ add_formula_for_column(sheet, '利润', '=IF(AND(ISNUMBER(G3),P3="收入结算"),F3-G3*D3,0)', 3)
1753
+ add_formula_for_column(sheet, '售出数量', '=IF(AND(ISNUMBER(G3),P3="收入结算"),D3,0)', 3)
1754
+ add_formula_for_column(sheet, '售出金额', '=IF(AND(ISNUMBER(G3),P3="收入结算"),F3,0)', 3)
1755
+ add_formula_for_column(sheet, '售出成本', '=IF(AND(ISNUMBER(G3),P3="收入结算"),D3 * G3,0)', 3)
1756
+ add_sum_for_cell(sheet, ['数量', '金额', '利润', '售出数量', '售出金额', '售出成本'])
1757
+ column_to_left(sheet, ['平台SKU', '商家SKU', '属性集'])
1758
+ column_to_right(sheet, ['单价', '金额', '利润'])
1759
+ hidden_columns(sheet, ['SKU图片'])
1760
+ close_excel(app, wb)
1761
+
1762
+ summary_list = self.calc_month_sales_percentage(month_data)
1763
+
1764
+ sheet_name = f'{last_month}月销售占比'
1765
+
1766
+ write_data(excel_path_month, sheet_name, sort_by_column(summary_list, 6, 2))
1767
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1768
+ set_title_style(sheet, 2)
1769
+ set_body_style(sheet, 3)
1770
+ add_borders(sheet)
1771
+ format_to_money(sheet, ['金额', '利润'])
1772
+ format_to_percent(sheet, ['占比'])
1773
+ add_sum_for_cell(sheet, ['利润', '售出数量', '售出金额'])
1774
+ column_to_left(sheet, ['商家SKU'])
1775
+ column_to_right(sheet, ['金额', '利润'])
1776
+ InsertImageV2(sheet, ['SKU图片'], 'shein', 90, None, None, True, 3)
1777
+ close_excel(app, wb)
1778
+
1779
+ stock_data = [[
1780
+ '月份', 'SKC', '供方货号', '平台SKU', '商家SKU', '属性集', '期初库存数量', '期初库存金额', '入库数量',
1781
+ '入库金额', '出库数量', '出库金额', '期末库存数量', '期末库存金额', '单价成本', '出库成本总额',
1782
+ '希音仓成本总额', '出库利润', '出库利润率', '供应商名称', '店铺账号', '店铺别名'
1783
+ ], [
1784
+ '合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
1785
+ ]]
1786
+
1787
+ for stock_item in shein_stock_list:
1788
+ row_item = []
1789
+ row_item.append(stock_item['reportDate'])
1790
+ row_item.append(stock_item['skc'])
1791
+ row_item.append(stock_item['supplierCode'])
1792
+ row_item.append(stock_item['skuCode'])
1793
+ row_item.append(stock_item['supplierSku'])
1794
+ row_item.append(stock_item['suffixZh'])
1795
+ row_item.append(stock_item['beginBalanceCnt'])
1796
+ row_item.append(stock_item['beginBalanceAmount'])
1797
+ row_item.append(stock_item['inCnt'])
1798
+ row_item.append(stock_item['inAmount'])
1799
+ row_item.append(stock_item['outCnt'])
1800
+ row_item.append(stock_item['outAmount'])
1801
+ row_item.append(stock_item['endBalanceCnt'])
1802
+ row_item.append(stock_item['endBalanceAmount'])
1803
+ row_item.append(stock_item['cost_price'])
1804
+ row_item.append('')
1805
+ row_item.append('')
1806
+ row_item.append('')
1807
+ row_item.append('')
1808
+ row_item.append(stock_item['supplierName'])
1809
+ row_item.append(store_username)
1810
+ row_item.append(store_name)
1811
+ stock_data.append(row_item)
1812
+
1813
+ sheet_name = f'{last_month}月库存结余'
1814
+ write_dict_to_file_ex(f'{self.config.auto_dir}/shein/cache/sheet_{last_month}_库存结余.json', {store_username: stock_data[:1] + stock_data[2:]}, [store_username])
1815
+
1816
+ write_data(excel_path_month, sheet_name, sort_by_column(stock_data, 11, 2))
1817
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1818
+ set_title_style(sheet, 2)
1819
+ set_body_style(sheet, 3)
1820
+ add_borders(sheet)
1821
+ format_to_money(sheet, ['金额', '总额', '成本', '出库利润'])
1822
+ column_to_right(sheet, ['金额', '总额', '成本', '出库利润'])
1823
+ format_to_percent(sheet, ['利润率'])
1824
+ column_to_left(sheet, ['供方货号', '平台SKU', '商家SKU', '属性集'])
1825
+ add_sum_for_cell(sheet, ['期初库存数量', '期初库存金额', '入库数量', '入库金额', '出库数量', '出库金额', '期末库存数量', '期末库存金额', '出库成本总额', '希音仓成本总额', '出库利润'])
1826
+ add_formula_for_column(sheet, '出库成本总额', '=K3*O3', 3)
1827
+ add_formula_for_column(sheet, '希音仓成本总额', '=M3*O3', 3)
1828
+ add_formula_for_column(sheet, '出库利润', '=L3-P3', 3)
1829
+ add_formula_for_column(sheet, '出库利润率', '=IF(L3 > 0,R3/L3,0)', 3)
1830
+ sheet.autofit()
1831
+ close_excel(app, wb)
1832
+
1833
+ replenish_data = [[
1834
+ "补扣款单号", "款项类型", "补扣款分类", "对单类型", "关联单据", "单价", "数量", "总金额", "币种", "创建时间",
1835
+ "单据状态", "关联报账单", "拒绝原因", "确认/拒绝时间", "操作人", "会计日期", "是否可报账", "申诉单号",
1836
+ "公司主体", "出口模式", "备注", "供货商名称", "店铺账号", "店铺别名"
1837
+ ], [
1838
+ "合计", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
1839
+ ]]
1840
+
1841
+ for replenish_item in shein_replenish_list:
1842
+ row_item = []
1843
+ row_item.append(replenish_item['replenishNo'])
1844
+ row_item.append(replenish_item['replenishTypeName'])
1845
+ row_item.append(replenish_item['categoryName'])
1846
+ row_item.append(replenish_item['toOrderTypeName'])
1847
+ row_item.append(replenish_item['relationNo'])
1848
+ row_item.append(replenish_item['unitPrice'])
1849
+ row_item.append(replenish_item['quantity'])
1850
+ row_item.append(replenish_item['amount'])
1851
+ row_item.append(replenish_item['currencyCode'])
1852
+ row_item.append(replenish_item['addTime'])
1853
+ row_item.append(replenish_item['replenishStatusName'])
1854
+ row_item.append(replenish_item['reportOrderNo'])
1855
+ row_item.append(replenish_item['refuseReason'])
1856
+ row_item.append(replenish_item['decisionTime'])
1857
+ row_item.append(replenish_item['operator'])
1858
+ row_item.append(replenish_item['accountDate'])
1859
+ row_item.append(replenish_item['reportableName'])
1860
+ row_item.append(replenish_item['billNo'])
1861
+ row_item.append(replenish_item['companyName'])
1862
+ row_item.append(replenish_item['exportingModeName'])
1863
+ row_item.append(replenish_item['remark'])
1864
+ row_item.append(supplierName)
1865
+ row_item.append(store_username)
1866
+ row_item.append(store_name)
1867
+ replenish_data.append(row_item)
1868
+
1869
+ sheet_name = f'{last_month}月补扣款列表'
1870
+
1871
+ write_dict_to_file_ex(f'{self.config.auto_dir}/shein/cache/sheet_{last_month}_补扣款列表.json', {store_username: replenish_data[:1] + replenish_data[2:]}, [store_username])
1872
+
1873
+ write_data(excel_path_month, sheet_name, sort_by_column(replenish_data, 2, 2))
1874
+
1875
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1876
+ set_title_style(sheet, 2)
1877
+ set_body_style(sheet, 3)
1878
+ add_borders(sheet)
1879
+ format_to_money(sheet, ['金额', '单价'])
1880
+ column_to_right(sheet, ['金额', '单价'])
1881
+ format_to_datetime(sheet, ['时间'])
1882
+ add_sum_for_cell(sheet, ['总金额'])
1883
+ sheet.autofit()
1884
+ close_excel(app, wb)
1885
+
1886
+ return_data = [[
1887
+ "退货单号", "退货计划单号", "处理类型", "发起原因", "说明", "状态", "退货方式", "退货仓库", "商家货号", "SKC",
1888
+ "待退货总数", "实际退货/报废总数", "签收时间", "创建时间", "运单号", "退货联系人", "联系人手机号", "退货地址"
1889
+ ], [
1890
+ "合计", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
1891
+ ]]
1892
+
1893
+ if len(shein_return_list) > 0:
1894
+ log(shein_return_list)
1895
+ for return_item in shein_return_list:
1896
+ row_item = []
1897
+ log(return_item)
1898
+ row_item.append(return_item['returnOrderNo'])
1899
+ row_item.append(return_item['returnPlanNo'])
1900
+ row_item.append(return_item['returnOrderTypeName'])
1901
+ row_item.append(return_item['returnReasonTypeName'])
1902
+ row_item.append(return_item['returnReason'])
1903
+ row_item.append(return_item['returnOrderStatusName'])
1904
+ row_item.append(return_item['returnWayTypeName'])
1905
+ row_item.append(return_item['warehouseName'])
1906
+ row_item.append(','.join(return_item['supplierCodeList']))
1907
+ row_item.append(','.join(return_item['skcNameList']))
1908
+ row_item.append(return_item['waitReturnQuantity'])
1909
+ row_item.append(return_item['returnQuantity'])
1910
+ row_item.append(return_item['signTime'])
1911
+ row_item.append(return_item['addTime'])
1912
+ row_item.append(return_item['expressNoList'])
1913
+ row_item.append(return_item['sellerContract'])
1914
+ row_item.append(return_item['sellerContractPhone'])
1915
+ row_item.append(return_item['returnAddress'])
1916
+ return_data.append(row_item)
1917
+
1918
+ sheet_name = f'{last_month}月退货与报废单列表'
1919
+
1920
+ write_data(excel_path_month, sheet_name, return_data, ['O', 'Q'])
1921
+
1922
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1923
+ set_title_style(sheet, 2)
1924
+ set_body_style(sheet, 3)
1925
+ add_borders(sheet)
1926
+ format_to_datetime(sheet, ['时间'])
1927
+ add_sum_for_cell(sheet, ['实际退货/报废总数'])
1928
+ sheet.autofit()
1929
+ close_excel(app, wb)
1930
+
1931
+ ###############################退供#######################################
1932
+ month_data = [[
1933
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '成本总额', '添加时间', '业务单号',
1934
+ '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', '店铺账号', '店铺别名', 'SKU图片',
1935
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']]
1936
+ log('len(back_list)', len(shein_back_list))
1937
+ for month_item in shein_back_list:
1938
+ row_item = []
1939
+ platform_sku = month_item['sku']
1940
+ row_item.append(platform_sku)
1941
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1942
+ row_item.append(supplier_sku)
1943
+ row_item.append(month_item['suffixZh'])
1944
+ row_item.append(month_item['quantity'])
1945
+ row_item.append(month_item['cost'])
1946
+ row_item.append(month_item['amount'])
1947
+ row_item.append(month_item['cost_price'])
1948
+ row_item.append('')
1949
+ row_item.append(month_item['addTime'])
1950
+ row_item.append(month_item['businessNo'])
1951
+ row_item.append(month_item['billNo'])
1952
+ row_item.append(month_item['displayChangeTypeName'])
1953
+ row_item.append(month_item['settleTypeName'])
1954
+ row_item.append(month_item['skc'])
1955
+ row_item.append(month_item['supplierCode'])
1956
+ row_item.append(month_item['supplierName'])
1957
+ row_item.append(store_username)
1958
+ row_item.append(store_name)
1959
+ row_item.append(month_item['sku_img'])
1960
+ month_data.append(row_item)
1961
+
1962
+ sheet_name = f'{last_month}月退供明细'
1963
+ write_dict_to_file_ex(f'{self.config.auto_dir}/shein/cache/sheet_{last_month}_退供列表.json', {store_username: month_data[:1] + month_data[2:]}, [store_username])
1964
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 2, 2))
1965
+
1966
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1967
+ set_title_style(sheet, 2)
1968
+ set_body_style(sheet, 3)
1969
+ add_borders(sheet)
1970
+ format_to_money(sheet, ['金额', '单价', '总额'])
1971
+ column_to_right(sheet, ['金额', '单价', '总额'])
1972
+ format_to_datetime(sheet, ['时间'])
1973
+ add_sum_for_cell(sheet, ['数量', '金额', '成本总额'])
1974
+ add_formula_for_column(sheet, '成本总额', '=G3*D3', 3)
1975
+ hidden_columns(sheet, ['SKU图片'])
1976
+ sheet.autofit()
1977
+ close_excel(app, wb)
1978
+
1979
+ ###############################不结算#######################################
1980
+ month_data = [[
1981
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '成本总额', '添加时间', '业务单号',
1982
+ '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', '店铺账号', '店铺别名', 'SKU图片',
1983
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
1984
+ ]]
1985
+ log('len(shein_no_settlement_list)', len(shein_no_settlement_list))
1986
+ for month_item in shein_no_settlement_list:
1987
+ row_item = []
1988
+ platform_sku = month_item['sku']
1989
+ row_item.append(platform_sku)
1990
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1991
+ row_item.append(supplier_sku)
1992
+ row_item.append(month_item['suffixZh'])
1993
+ row_item.append(month_item['quantity'])
1994
+ row_item.append(month_item['cost'])
1995
+ row_item.append(month_item['amount'])
1996
+ row_item.append(month_item['cost_price'])
1997
+ row_item.append('')
1998
+ row_item.append(month_item['addTime'])
1999
+ row_item.append(month_item['businessNo'])
2000
+ row_item.append(month_item['billNo'])
2001
+ row_item.append(month_item['displayChangeTypeName'])
2002
+ row_item.append(month_item['settleTypeName'])
2003
+ row_item.append(month_item['skc'])
2004
+ row_item.append(month_item['supplierCode'])
2005
+ row_item.append(month_item['supplierName'])
2006
+ row_item.append(store_username)
2007
+ row_item.append(store_name)
2008
+ row_item.append(month_item['sku_img'])
2009
+ month_data.append(row_item)
2010
+
2011
+ sheet_name = f'{last_month}月不结算明细'
2012
+
2013
+ write_dict_to_file_ex(f'{self.config.auto_dir}/shein/cache/sheet_{last_month}_不结算列表.json', {store_username: month_data[:1] + month_data[2:]}, [store_username])
2014
+
2015
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 2, 2))
2016
+
2017
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
2018
+ set_title_style(sheet, 2)
2019
+ set_body_style(sheet, 3)
2020
+ add_borders(sheet)
2021
+ format_to_money(sheet, ['金额', '单价', '总额'])
2022
+ column_to_right(sheet, ['金额', '单价', '总额'])
2023
+ format_to_datetime(sheet, ['时间'])
2024
+ add_sum_for_cell(sheet, ['数量', '金额', '成本总额'])
2025
+ add_formula_for_column(sheet, '成本总额', '=G3*D3', 3)
2026
+ hidden_columns(sheet, ['SKU图片'])
2027
+ sheet.autofit()
2028
+ close_excel(app, wb)
2029
+
2030
+ sheet_name = f'{last_month}月利润汇总'
2031
+ # 建立利润汇总sheet页
2032
+ write_json_to_excel('excel_json_profit_detail.json', excel_path_month, sheet_name)
2033
+
2034
+ # 填入数据 销售数量
2035
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
2036
+ delete_sheet_if_exists(wb, 'Sheet1')
2037
+ move_sheet_to_position(wb, sheet_name, 1)
2038
+ wb.save()
2039
+ sheet.activate()
2040
+
2041
+ target_month = find_column_by_data(sheet, 2, last_month)
2042
+ sheet.range(f'{target_month}3').value = f"='{last_month}月销售明细'!I2"
2043
+
2044
+ sheet.range(f'{target_month}4').number_format = f"¥#,##0.00;¥-#,##0.00"
2045
+ sheet.range(f'{target_month}4').value = f"='{last_month}月销售明细'!J2"
2046
+
2047
+ sheet.range(f'A5').value = f"销售成本"
2048
+ sheet.range(f'{target_month}5').number_format = f"¥#,##0.00;¥-#,##0.00"
2049
+ sheet.range(f'{target_month}5').value = f"='{last_month}月销售明细'!K2"
2050
+
2051
+ sheet.range(f'A6').value = f"销售利润"
2052
+ sheet.range(f'{target_month}6').number_format = f"¥#,##0.00;¥-#,##0.00"
2053
+ sheet.range(f'{target_month}6').value = f"='{last_month}月销售明细'!H2"
2054
+
2055
+ # sheet.range(f'{target_month}6').number_format = f"¥#,##0.00;¥-#,##0.00"
2056
+ # sheet.range(f'{target_month}6').value = f"=-'{last_month}月退货与报废单列表'!L2 * 3"
2057
+ sheet.range(f'{target_month}7').number_format = f"¥#,##0.00;¥-#,##0.00"
2058
+ sheet.range(f'{target_month}7').value = f"=-'{last_month}月补扣款列表'!H2"
2059
+ sheet.range(f'{target_month}8').number_format = f"¥#,##0.00;¥-#,##0.00"
2060
+ sheet.range(f'{target_month}9').number_format = f"¥#,##0.00;¥-#,##0.00"
2061
+ sheet.range(f'{target_month}9').value = f"=SUM({target_month}6:{target_month}8)"
2062
+ sheet.range(f'{target_month}10').number_format = f"¥#,##0.00;¥-#,##0.00"
2063
+ sheet.range(f'{target_month}10').value = f"='{last_month}月库存结余'!Q2"
2064
+
2065
+ sheet.range('A1').value = f'2025年{last_month}月 shein 利润汇总表 {store_name}'
2066
+ sheet.range(f'{target_month}:{target_month}').autofit()
2067
+ wb.save()
2068
+ close_excel(app, wb)
2069
+
2070
+ def write_summary_algorithm_1(self):
2071
+ excel_path = self.config.excel_shein_finance_month_report_summary
2072
+
2073
+ sheet_name = '总表-算法1'
2074
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2075
+ total_data = []
2076
+ header = ['店铺账号', '店铺别名']
2077
+ for mall_id, excel_data in dict_store.items():
2078
+ total_data += [[mall_id, excel_data]]
2079
+
2080
+ log(total_data)
2081
+ filtered_value = [header] + total_data
2082
+ log(filtered_value)
2083
+ filtered_value = add_suffixed_column(filtered_value, '店长', '')
2084
+ filtered_value = add_suffixed_column(filtered_value, '出库金额', '')
2085
+ filtered_value = add_suffixed_column(filtered_value, '出库成本', '')
2086
+ filtered_value = add_suffixed_column(filtered_value, '不结算金额', '')
2087
+ filtered_value = add_suffixed_column(filtered_value, '不结算成本', '')
2088
+ filtered_value = add_suffixed_column(filtered_value, '实际出库金额', '')
2089
+ filtered_value = add_suffixed_column(filtered_value, '实际出库成本', '')
2090
+ filtered_value = add_suffixed_column(filtered_value, '补款', '')
2091
+ filtered_value = add_suffixed_column(filtered_value, '扣款', '')
2092
+ filtered_value = add_suffixed_column(filtered_value, '线下运费', '')
2093
+ filtered_value = add_suffixed_column(filtered_value, '侵权扣款', '')
2094
+ filtered_value = add_suffixed_column(filtered_value, '希音仓成本总额', '')
2095
+ filtered_value = add_suffixed_column(filtered_value, '毛利', '')
2096
+
2097
+ # 匹配店铺店长
2098
+ dict_store_manager_shein = self.config.shein_store_manager
2099
+ for row in filtered_value:
2100
+ mall_name = row[0]
2101
+ if mall_name == '店铺账号':
2102
+ continue
2103
+ row[2] = dict_store_manager_shein.get(str(mall_name).lower())
2104
+ self.write_to_one(filtered_value, excel_path, sheet_name)
2105
+
2106
+ def write_summary_algorithm_2(self):
2107
+ excel_path = self.config.excel_shein_finance_month_report_summary
2108
+
2109
+ app, wb, sheet = open_excel(excel_path, 2)
2110
+
2111
+ sheet_name = '总表-算法2'
2112
+ # 将目标工作表移动到第一个工作表之前
2113
+ sheet.api.Move(Before=wb.sheets[0].api)
2114
+ wb.save()
2115
+ close_excel(app, wb)
2116
+
2117
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2118
+ total_data = []
2119
+ header = ['店铺账号', '店铺别名']
2120
+ for mall_id, excel_data in dict_store.items():
2121
+ total_data += [[mall_id, excel_data]]
2122
+
2123
+ filtered_value = [header] + total_data
2124
+ filtered_value = add_suffixed_column(filtered_value, '店长', '')
2125
+ filtered_value = add_suffixed_column(filtered_value, '出库金额', '')
2126
+ filtered_value = add_suffixed_column(filtered_value, '出库成本', '')
2127
+ filtered_value = add_suffixed_column(filtered_value, '退供金额', '')
2128
+ filtered_value = add_suffixed_column(filtered_value, '退供成本', '')
2129
+ filtered_value = add_suffixed_column(filtered_value, '实际出库金额', '')
2130
+ filtered_value = add_suffixed_column(filtered_value, '实际出库成本', '')
2131
+ filtered_value = add_suffixed_column(filtered_value, '补款', '')
2132
+ filtered_value = add_suffixed_column(filtered_value, '扣款', '')
2133
+ filtered_value = add_suffixed_column(filtered_value, '线下运费', '')
2134
+ filtered_value = add_suffixed_column(filtered_value, '侵权扣款', '')
2135
+ filtered_value = add_suffixed_column(filtered_value, '希音仓成本总额', '')
2136
+ filtered_value = add_suffixed_column(filtered_value, '毛利', '')
2137
+
2138
+ # 匹配店铺店长
2139
+ dict_store_manager_shein = self.config.shein_store_manager
2140
+ for row in filtered_value:
2141
+ mall_name = row[0]
2142
+ if mall_name == '店铺账号':
2143
+ continue
2144
+ row[2] = dict_store_manager_shein.get(str(mall_name).lower())
2145
+ self.write_to_one(filtered_value, excel_path, sheet_name)
2146
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2147
+
2148
+ def sumary_part(self):
2149
+ excel_path = self.config.excel_shein_finance_month_report_summary
2150
+ src_directory = f'{self.config.auto_dir}/shein/cache'
2151
+ for file in os.listdir(src_directory):
2152
+ # 检查是否为文件且符合命名模式
2153
+ if file.startswith(f"sheet_{TimeUtils.get_last_month()}") and file.endswith(".json"):
2154
+ file_path = os.path.join(src_directory, file)
2155
+ filename = os.path.basename(file_path) # 获取 "tool.py"
2156
+ name = os.path.splitext(filename)[0]
2157
+ sheet_name = name.split('_')[2]
2158
+ dict = read_dict_from_file(file_path)
2159
+ total_data = []
2160
+ header = []
2161
+ for mall_id, excel_data in dict.items():
2162
+ header = excel_data[0]
2163
+ if len(excel_data) > 1:
2164
+ total_data += excel_data[1:]
2165
+
2166
+ filtered_value = [header] + total_data
2167
+ self.write_to_one(filtered_value, excel_path, f'{sheet_name}-汇总-{TimeUtils.get_last_month()}月')
2168
+
2169
+ def write_to_one(self, data, excel_path, sheet_name="Sheet1", header_column=None):
2170
+ write_data(excel_path, sheet_name, data)
2171
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2172
+ add_borders(sheet)
2173
+ format_to_money(sheet, ['金额', '成本'])
2174
+ format_to_datetime(sheet, ['时间'])
2175
+ if '库存结余' in sheet_name:
2176
+ format_to_percent(sheet, ['利润率'])
2177
+ format_to_month(sheet, ['月份'])
2178
+ add_formula_for_column(sheet, '出库成本总额', f'=IF(ISNUMBER(O2),K2*O2,0)', 2)
2179
+ add_formula_for_column(sheet, '希音仓成本总额', f'=IF(ISNUMBER(O2),M2*O2,0)', 2)
2180
+ add_formula_for_column(sheet, '出库利润', f'=L2-P2', 2)
2181
+ add_formula_for_column(sheet, '出库利润率', f'=IF(L2 > 0,R2/L2,0)', 2)
2182
+ if '退供列表' in sheet_name:
2183
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER(G2),D2*G2,0)', 2)
2184
+ if '不结算列表' in sheet_name:
2185
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER(G2),D2*G2,0)', 2)
2186
+ if '总表-算法1' in sheet_name:
2187
+ format_to_money(sheet, ['补款', '扣款', '线下运费', '侵权扣款', '毛利'])
2188
+ add_formula_for_column(sheet, '出库金额',
2189
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!L:L)",
2190
+ 2)
2191
+ add_formula_for_column(sheet, '出库成本',
2192
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!P:P)",
2193
+ 2)
2194
+ add_formula_for_column(sheet, '不结算金额',
2195
+ f"=SUMIF('不结算列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'不结算列表-汇总-{TimeUtils.get_last_month()}月'!F:F)",
2196
+ 2)
2197
+ add_formula_for_column(sheet, '不结算成本',
2198
+ f"=SUMIF('不结算列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'不结算列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2199
+ 2)
2200
+ add_formula_for_column(sheet, '实际出库金额', f"=D2-F2", 2)
2201
+ add_formula_for_column(sheet, '实际出库成本', f"=E2-G2", 2)
2202
+ # 补款:款项类型为"补款"的金额汇总(入账)
2203
+ add_formula_for_column(sheet, '补款',
2204
+ f"=SUMIFS('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法1'!A2,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!B:B,\"补款\")",
2205
+ 2)
2206
+ # 扣款:款项类型为"扣款"的金额汇总(出账)
2207
+ add_formula_for_column(sheet, '扣款',
2208
+ f"=SUMIFS('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法1'!A2,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!B:B,\"扣款\")",
2209
+ 2)
2210
+ add_formula_for_column(sheet, '希音仓成本总额',
2211
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!Q:Q)",
2212
+ 2)
2213
+ # 毛利 = 实际出库金额 - 实际出库成本 + 补款 - 扣款 - 线下运费 - 侵权扣款
2214
+ add_formula_for_column(sheet, '毛利', f"=H2-I2+J2-K2-L2-M2", 2)
2215
+ # 全是公式 无法排序
2216
+
2217
+ if '总表-算法2' in sheet_name:
2218
+ format_to_money(sheet, ['补款', '扣款', '线下运费', '侵权扣款', '毛利'])
2219
+ add_formula_for_column(sheet, '出库金额',
2220
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!L:L)",
2221
+ 2)
2222
+ add_formula_for_column(sheet, '出库成本',
2223
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!P:P)",
2224
+ 2)
2225
+ add_formula_for_column(sheet, '退供金额',
2226
+ f"=SUMIF('退供列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'退供列表-汇总-{TimeUtils.get_last_month()}月'!F:F)",
2227
+ 2)
2228
+ add_formula_for_column(sheet, '退供成本',
2229
+ f"=SUMIF('退供列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'退供列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2230
+ 2)
2231
+ add_formula_for_column(sheet, '实际出库金额', f"=D2-F2", 2)
2232
+ add_formula_for_column(sheet, '实际出库成本', f"=E2-G2", 2)
2233
+ # 补款:款项类型为"补款"的金额汇总(入账)
2234
+ add_formula_for_column(sheet, '补款',
2235
+ f"=SUMIFS('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法2'!A2,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!B:B,\"补款\")",
2236
+ 2)
2237
+ # 扣款:款项类型为"扣款"的金额汇总(出账)
2238
+ add_formula_for_column(sheet, '扣款',
2239
+ f"=SUMIFS('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法2'!A2,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!B:B,\"扣款\")",
2240
+ 2)
2241
+ add_formula_for_column(sheet, '希音仓成本总额',
2242
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!Q:Q)",
2243
+ 2)
2244
+ # 毛利 = 实际出库金额 - 实际出库成本 + 补款 - 扣款 - 线下运费 - 侵权扣款
2245
+ add_formula_for_column(sheet, '毛利', f"=H2-I2+J2-K2-L2-M2", 2)
2246
+
2247
+ move_sheet_to_position(wb, '总表-算法1', 1)
2248
+ move_sheet_to_position(wb, '总表-算法2', 1)
2249
+
2250
+ set_title_style(sheet, 1)
2251
+ wb.save()
2252
+ close_excel(app, wb)
2253
+
2254
+ def format_funds(self, sheet):
2255
+ beautify_title(sheet)
2256
+ column_to_right(sheet, ['金额', '汇总'])
2257
+ format_to_money(sheet, ['金额', '汇总'])
2258
+ add_sum_for_cell(sheet, ['在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '销售出库金额', '汇总'])
2259
+ add_formula_for_column(sheet, '汇总', '=SUM(D3:G3)', 3)
2260
+ sheet.autofit()
2261
+
2262
+ def format_bad_comment(self, sheet):
2263
+ beautify_title(sheet)
2264
+ column_to_left(sheet, ['商品信息'])
2265
+ autofit_column(sheet, ['买家评价', '时间信息', '标签关键词'])
2266
+ specify_column_width(sheet, ['买家评价', '商品信息'], 150 / 6)
2267
+ color_for_column(sheet, ['买家评分'], '红色')
2268
+ colorize_by_field(sheet, 'skc')
2269
+ add_borders(sheet)
2270
+ InsertImageV2(sheet, ['商品图片', '图1', '图2', '图3', '图4', '图5'])
2271
+
2272
+ def write_bad_comment(self):
2273
+ excel_path = create_file_path(self.config.excel_bad_comment)
2274
+ header = ['评价ID', '商品图片', '商品信息', '买家评分', '买家评价', '标签关键词', '区域', '时间信息', '有图', '图1',
2275
+ '图2', '图3', '图4', '图5', 'skc']
2276
+ summary_excel_data = [header]
2277
+
2278
+ cache_file = f'{self.config.auto_dir}/shein/dict/comment_list_{TimeUtils.today_date()}.json'
2279
+ dict = read_dict_from_file(cache_file)
2280
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2281
+
2282
+ for store_username, comment_list in dict.items():
2283
+ store_name = dict_store.get(store_username)
2284
+ sheet_name = store_name
2285
+
2286
+ store_excel_data = [header]
2287
+ for comment in comment_list:
2288
+ row_item = []
2289
+ row_item.append(f'{comment['commentId']}\n{store_name}')
2290
+ row_item.append(comment['goodsThumb'])
2291
+ product_info = f'属性:{comment["goodsAttribute"]}\n货号:{comment["goodSn"]}\nSPU:{comment["spu"]}\nSKC:{comment["skc"]}\nSKU:{comment["sku"]}'
2292
+ row_item.append(product_info)
2293
+ row_item.append(calculate_star_symbols(comment['goodsCommentStar']))
2294
+ row_item.append(comment['goodsCommentContent'])
2295
+ qualityLabel = '存在质量问题\n' if comment['isQualityLabel'] == 1 else ''
2296
+ bad_comment_label = qualityLabel + '\n'.join([item['labelName'] for item in comment['badCommentLabelList']])
2297
+
2298
+ row_item.append(bad_comment_label)
2299
+ row_item.append(comment['dataCenterName'])
2300
+ time_info = f'下单时间:{comment["orderTime"]}\n评论时间:{comment["commentTime"]}'
2301
+ row_item.append(time_info)
2302
+
2303
+ # 获取图片数量
2304
+ image_num = len(comment.get('goodsCommentImages', []))
2305
+ # 设置imgFlag值(如果comment中没有imgFlag字段,默认设为0)
2306
+ imgFlag = image_num if comment.get('imgFlag') == 1 else 0
2307
+ row_item.append(imgFlag)
2308
+
2309
+ images = comment.get('goodsCommentImages', [])
2310
+ for i in range(5):
2311
+ row_item.append(images[i] if i < len(images) else '')
2312
+
2313
+ row_item.append(comment['skc'])
2314
+
2315
+ store_excel_data.append(row_item)
2316
+ summary_excel_data.append(row_item)
2317
+
2318
+ # write_data(excel_path, sheet_name, store_excel_data)
2319
+ # format_bad_comment(excel_path, sheet_name)
2320
+
2321
+ sheet_name = 'Sheet1'
2322
+
2323
+ batch_excel_operations(excel_path, [
2324
+ (sheet_name, 'write', summary_excel_data),
2325
+ (sheet_name, 'format', self.format_bad_comment),
2326
+ ])
2327
+
2328
+ def write_funds(self):
2329
+ cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_{TimeUtils.today_date()}.json'
2330
+ dict = read_dict_from_file(cache_file)
2331
+ data = []
2332
+ for key, val in dict.items():
2333
+ data.append(val)
2334
+
2335
+ excel_path = create_file_path(self.config.excel_shein_fund)
2336
+ sheet_name = 'Sheet1'
2337
+ data.insert(0, ['汇总', '', '', '', '', '', '', '', '', ''])
2338
+ data.insert(0, ['店铺名称', '店铺账号', '店长', '在途商品金额', '在仓商品金额', '待结算金额', '可提现金额',
2339
+ '销售出库金额', '汇总', '导出时间'])
2340
+ batch_excel_operations(excel_path, [
2341
+ ('Sheet1', 'write', sort_by_column(data, 7, 2)),
2342
+ ('Sheet1', 'format', self.format_funds),
2343
+ ])
2344
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2345
+
2346
+ def format_skc_quality(self, sheet):
2347
+ beautify_title(sheet)
2348
+ colorize_by_field(sheet, 'skc')
2349
+ add_borders(sheet)
2350
+ InsertImageV2(sheet, ['商品图片'])
2351
+
2352
+ def sort_site_desc_by_sale_cnt_14d(self, data, reverse=True):
2353
+ """
2354
+ 对data中的site_desc_vo_list按照skc_site_sale_cnt_14d进行排序
2355
+
2356
+ 参数:
2357
+ data: 包含site_desc_vo_list的字典
2358
+ reverse: 是否倒序排序,默认为True(从大到小)
2359
+
2360
+ 返回:
2361
+ 排序后的data(原数据会被修改)
2362
+ """
2363
+ if 'site_desc_vo_list' in data and isinstance(data['site_desc_vo_list'], list):
2364
+ # 处理None值,将它们放在排序结果的最后
2365
+ data['site_desc_vo_list'].sort(
2366
+ key=lambda x: float('-inf') if x.get('skc_site_sale_cnt_14d') is None else x['skc_site_sale_cnt_14d'],
2367
+ reverse=reverse
2368
+ )
2369
+ return data
2370
+
2371
+ def write_skc_quality_estimate(self):
2372
+ excel_path = create_file_path(self.config.excel_skc_quality_estimate)
2373
+ header = ['店铺信息', '商品图片', '统计日期', '国家', '当日销量', '14日销量', '14日销量占比', '质量等级',
2374
+ '客评数/客评分', '差评数/差评率', '退货数/退货率', 'skc', 'skc当日销量', 'skc14日销量', 'skc14日销量占比']
2375
+ summary_excel_data = [header]
2376
+
2377
+ stat_date = TimeUtils.before_yesterday()
2378
+ cache_file = f'{self.config.auto_dir}/shein/dict/googs_estimate_{stat_date}.json'
2379
+ dict = read_dict_from_file(cache_file)
2380
+ if len(dict) == 0:
2381
+ log('昨日质量评估数据不存在')
2382
+ return
2383
+
2384
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2385
+
2386
+ operations = []
2387
+ for store_username, skc_list in dict.items():
2388
+ store_name = dict_store.get(store_username)
2389
+ sheet_name = store_name
2390
+
2391
+ store_excel_data = [header]
2392
+ for skc_item in skc_list:
2393
+ sorted_skc_item = self.sort_site_desc_by_sale_cnt_14d(skc_item, True)
2394
+ # for site in sorted_skc_item['site_desc_vo_list']:
2395
+ # print(f"{site['country_site']}: {site['skc_site_sale_cnt_14d']}")
2396
+ # continue
2397
+ store_info = f'{store_name}'
2398
+ skc = sorted_skc_item['skc']
2399
+ sites = sorted_skc_item['site_desc_vo_list']
2400
+ skc_sale_cnt = sorted_skc_item['skc_sale_cnt']
2401
+ skc_sale_cnt_14d = sorted_skc_item['skc_sale_cnt_14d']
2402
+ skc_sale_rate_14d = sorted_skc_item['skc_sale_rate_14d']
2403
+ for site in sites:
2404
+ row_item = []
2405
+ row_item.append(store_info)
2406
+ row_item.append(skc_item['goods_image'])
2407
+ row_item.append(stat_date)
2408
+ row_item.append(site['country_site'])
2409
+ row_item.append(site['skc_site_sale_cnt'])
2410
+ cnt_14d = site['skc_site_sale_cnt_14d']
2411
+ if cnt_14d is None or cnt_14d <= 0:
2412
+ continue
2413
+ row_item.append(cnt_14d)
2414
+ row_item.append(site['skc_site_sale_rate_14d'])
2415
+ row_item.append(site['quality_level'])
2416
+ customer_info = f'{site["customer_evaluate_num"]}/{site["customer_evaluate_score"][:-1]}'
2417
+ row_item.append(customer_info)
2418
+ negative_info = f'{site["negative_quantity"]}/{site["negative_percent"]}'
2419
+ row_item.append(negative_info)
2420
+ return_info = f'{site["goods_return_quantity"]}/{site["goods_return_percent"]}'
2421
+ row_item.append(return_info)
2422
+ row_item.append(skc)
2423
+ row_item.append(skc_sale_cnt)
2424
+ row_item.append(skc_sale_cnt_14d)
2425
+ row_item.append(skc_sale_rate_14d)
2426
+ store_excel_data.append(row_item)
2427
+ summary_excel_data.append(row_item)
2428
+
2429
+ operations.append((
2430
+ sheet_name, 'write', store_excel_data
2431
+ ))
2432
+ operations.append((
2433
+ sheet_name, 'format', self.format_skc_quality
2434
+ ))
2435
+ operations.append((
2436
+ 'Sheet1', 'delete'
2437
+ ))
2438
+ batch_excel_operations(excel_path, operations)
2439
+
2440
+ # 添加月度sheet操作 - 自定义操作函数
2441
+ def write_monthly_data(self, sheet, data, name):
2442
+ # 写入数据到A5位置(月度数据从A列开始)
2443
+ sheet.range('A5').value = data
2444
+ # 设置标题
2445
+ sheet.range('A1').value = f'{name}SHEIN{TimeUtils.get_current_month()}月店铺数据'
2446
+
2447
+ def write_sales_data(self):
2448
+ yesterday = TimeUtils.get_yesterday()
2449
+ model = SheinStoreSalesDetailManager(self.config.database_url)
2450
+ records = model.get_one_day_records(yesterday, SheinStoreSalesDetail.sales_amount.desc())
2451
+ data_day = []
2452
+ dict_store_manager_shein = self.config.shein_store_manager
2453
+ dict_store_name = read_dict_from_file(self.config.shein_store_alias)
2454
+
2455
+ # 准备每日汇总数据
2456
+ for record in records:
2457
+ store_data = []
2458
+ store_data.append(dict_store_name.get(record.store_username))
2459
+ store_data.append(dict_store_manager_shein.get(str(record.store_username).lower(), '-'))
2460
+ store_data.append(record.sales_num)
2461
+ store_data.append(record.sales_num_inc)
2462
+ store_data.append(record.sales_amount)
2463
+ store_data.append(record.sales_amount_inc)
2464
+ store_data.append(record.visitor_num)
2465
+ store_data.append(record.visitor_num_inc)
2466
+ store_data.append(record.bak_A_num)
2467
+ store_data.append(record.bak_A_num_inc)
2468
+ store_data.append(record.new_A_num)
2469
+ store_data.append(record.new_A_num_inc)
2470
+ store_data.append(record.on_sales_product_num)
2471
+ store_data.append(record.on_sales_product_num_inc)
2472
+ store_data.append(record.wait_shelf_product_num)
2473
+ store_data.append(record.wait_shelf_product_num_inc)
2474
+ store_data.append(record.upload_product_num)
2475
+ store_data.append(record.upload_product_num_inc)
2476
+ store_data.append(record.sold_out_product_num)
2477
+ store_data.append(record.shelf_off_product_num)
2478
+ data_day.append(store_data)
2479
+
2480
+ excel_path = create_file_path(self.config.excel_daily_report)
2481
+ delete_file(excel_path)
2482
+ sheet_name_first = 'SHEIN销售部每日店铺情况'
2483
+
2484
+ # 准备批量操作列表
2485
+ base_operations = []
2486
+
2487
+ # 添加每日汇总sheet的操作 - 自定义操作函数
2488
+ def write_daily_data(sheet):
2489
+ # 写入数据到B5位置,保持原有格式
2490
+ sheet.range('B5').value = data_day
2491
+ # 设置标题
2492
+ sheet.range('A1').value = f'销售部SHEIN{TimeUtils.get_current_month()}月店铺数据'
2493
+ # 设置日期和合并
2494
+ sheet.range('A4').value = f'{TimeUtils.format_date_cross_platform(yesterday)}\n({TimeUtils.get_chinese_weekday(yesterday)})'
2495
+
2496
+ base_operations.append((sheet_name_first, 'format', write_daily_data))
2497
+ base_operations.append((sheet_name_first, 'format', self._format_daily_summary_sheet, yesterday, len(data_day)))
2498
+ base_operations.append((sheet_name_first, 'move', 1))
2499
+ base_operations.append(('Sheet1', 'delete'))
2500
+
2501
+ # 获取店铺列表并准备月度数据
2502
+ store_list = model.get_distinct_store_sales_list()
2503
+
2504
+ # 准备所有店铺的数据
2505
+ store_operations_data = []
2506
+ for store in store_list:
2507
+ store_username = store[0]
2508
+ store_name = dict_store_name.get(store_username)
2509
+ records = model.get_one_month_records(TimeUtils.get_current_year(), TimeUtils.get_current_month(), store_username)
2510
+
2511
+ data_month = []
2512
+ for record in records:
2513
+ store_data = []
2514
+ store_data.append(record.day)
2515
+ store_data.append(record.sales_num)
2516
+ store_data.append(record.sales_num_inc)
2517
+ store_data.append(record.sales_amount)
2518
+ store_data.append(record.sales_amount_inc)
2519
+ store_data.append(record.visitor_num)
2520
+ store_data.append(record.visitor_num_inc)
2521
+ store_data.append(record.bak_A_num)
2522
+ store_data.append(record.bak_A_num_inc)
2523
+ store_data.append(record.new_A_num)
2524
+ store_data.append(record.new_A_num_inc)
2525
+ store_data.append(record.on_sales_product_num)
2526
+ store_data.append(record.on_sales_product_num_inc)
2527
+ store_data.append(record.wait_shelf_product_num)
2528
+ store_data.append(record.wait_shelf_product_num_inc)
2529
+ store_data.append(record.upload_product_num)
2530
+ store_data.append(record.upload_product_num_inc)
2531
+ store_data.append(record.sold_out_product_num)
2532
+ store_data.append(record.shelf_off_product_num)
2533
+ # store_data.append(record.remark) # 月度数据不包含备注列,保持19列
2534
+ data_month.append(store_data)
2535
+
2536
+ store_operations_data.append((store_name, data_month))
2537
+
2538
+ # 构建所有操作列表
2539
+ operations = base_operations.copy()
2540
+
2541
+ # 添加店铺操作
2542
+ for store_name, data_month in store_operations_data:
2543
+ # 清理店铺名称
2544
+ clean_store_name = self._clean_sheet_name(store_name)
2545
+ operations.append((clean_store_name, 'format', self.write_monthly_data, data_month, clean_store_name))
2546
+ operations.append((clean_store_name, 'format', self._format_store_monthly_sheet, clean_store_name, len(data_month)))
2547
+
2548
+ # 添加最后激活操作
2549
+ operations.append((sheet_name_first, 'active'))
2550
+
2551
+ # 执行批量操作(内部会自动分批处理)
2552
+ success = batch_excel_operations(excel_path, operations)
2553
+
2554
+ if success:
2555
+ # 发送文件到企业微信
2556
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2557
+ log(f"销售数据写入完成: {excel_path}")
2558
+ else:
2559
+ log(f"销售数据写入失败: {excel_path}")
2560
+
2561
+ def _clean_sheet_name(self, name):
2562
+ """
2563
+ 清理工作表名称,移除Excel不支持的字符
2564
+ """
2565
+ if not name:
2566
+ return "DefaultSheet"
2567
+
2568
+ # Excel工作表名称限制:不能包含 [ ] : * ? / \ 字符,且长度不超过31字符
2569
+ invalid_chars = ['[', ']', ':', '*', '?', '/', '\\']
2570
+ clean_name = name
2571
+
2572
+ for char in invalid_chars:
2573
+ clean_name = clean_name.replace(char, '_')
2574
+
2575
+ # 限制长度为31字符
2576
+ if len(clean_name) > 31:
2577
+ clean_name = clean_name[:28] + "..."
2578
+
2579
+ # 确保不为空
2580
+ if not clean_name.strip():
2581
+ clean_name = "Sheet"
2582
+
2583
+ return clean_name
2584
+
2585
+ def _format_daily_summary_sheet(self, sheet, yesterday, data_length):
2586
+ """格式化每日汇总sheet"""
2587
+ las_row = data_length + 4 # 数据从第5行开始,4行header
2588
+
2589
+ # 设置数据区域格式(从B5开始,因为数据写入到B5)
2590
+ sheet.range(f'B5:U{las_row}').api.Font.Color = 0x000000
2591
+ sheet.range(f'B5:U{las_row}').api.Font.Bold = False
2592
+
2593
+ # 设置A4日期列的格式和合并
2594
+ sheet.range('A4').column_width = 16
2595
+ sheet.range('A4').api.VerticalAlignment = -4160 # 垂直顶部对齐
2596
+ sheet.range(f'A4:A{las_row}').merge()
2597
+
2598
+ # 设置负数为红色(E,G,I,K列)
2599
+ self._set_negative_numbers_red(sheet, ['E', 'G', 'I', 'K'], 5, las_row)
2600
+
2601
+ # 格式化表头
2602
+ self._format_daily_header(sheet, las_row)
2603
+
2604
+ # 设置汇总公式和格式
2605
+ self._set_summary_formulas(sheet, las_row)
2606
+
2607
+ # 设置边框
2608
+ self._set_borders(sheet, f'A2:U{las_row}')
2609
+
2610
+ sheet.autofit()
2611
+
2612
+ def _format_store_monthly_sheet(self, sheet, store_name, data_length):
2613
+ """格式化店铺月度sheet"""
2614
+ las_row = data_length + 4 # 数据从第5行开始,4行header
2615
+
2616
+ # 数据已经写入,现在进行格式化
2617
+ # 设置数据区域格式(从A5开始到S列,月度数据是19列)
2618
+ sheet.range(f'A5:S{las_row}').api.Font.Color = 0x000000
2619
+ sheet.range(f'A5:S{las_row}').api.Font.Bold = False
2620
+
2621
+ # 格式化表头
2622
+ self._format_monthly_header(sheet, las_row)
2623
+
2624
+ # 设置汇总公式和格式
2625
+ self._set_monthly_summary_formulas(sheet, las_row)
2626
+
2627
+ # 设置边框
2628
+ self._set_borders(sheet, f'A2:S{las_row}')
2629
+
2630
+ sheet.autofit()
2631
+
2632
+ def _set_negative_numbers_red(self, sheet, columns, start_row, end_row):
2633
+ """设置负数为红色"""
2634
+ for col in columns:
2635
+ column_range = sheet.range(f'{col}{start_row}:{col}{end_row}')
2636
+ for cell in column_range:
2637
+ if cell.value is not None and cell.value < 0:
2638
+ cell.font.color = (255, 0, 0)
2639
+
2640
+ def _format_daily_header(self, sheet, las_row):
2641
+ """格式化每日汇总表头,完全按照原始格式"""
2642
+ # 第一行:标题
2643
+ range_one = f'A1:U1'
2644
+ sheet.range(range_one).merge()
2645
+ sheet.range(range_one).api.Font.Size = 24
2646
+ sheet.range(range_one).api.Font.Bold = True
2647
+ sheet.range(range_one).api.HorizontalAlignment = -4108
2648
+ sheet.range(range_one).api.VerticalAlignment = -4108
2649
+
2650
+ # 第二行:分类标题
2651
+ range_two_part_1 = f'A2:C2'
2652
+ range_two_part_2 = f'D2:O2'
2653
+ range_two_part_3 = f'P2:U2'
2654
+ sheet.range(range_two_part_1).merge()
2655
+ sheet.range(range_two_part_2).merge()
2656
+ sheet.range(range_two_part_3).merge()
2657
+
2658
+ sheet.range(f'A2:C3').color = 0x47a100
2659
+
2660
+ sheet.range('D2').value = '店铺的结果和稳定性'
2661
+ sheet.range(range_two_part_2).api.Font.Size = 16
2662
+ sheet.range(range_two_part_2).api.Font.Color = 0xFFFFFF
2663
+ sheet.range(range_two_part_2).api.Font.Bold = True
2664
+ sheet.range(range_two_part_2).api.HorizontalAlignment = -4108
2665
+ sheet.range(range_two_part_2).api.VerticalAlignment = -4108
2666
+ sheet.range(f'D2:O3').color = 0x0000FF
2667
+
2668
+ sheet.range('P2').value = '上新的质量和数量'
2669
+ sheet.range(range_two_part_3).api.Font.Size = 16
2670
+ sheet.range(range_two_part_3).api.Font.Color = 0xFFFFFF
2671
+ sheet.range(range_two_part_3).api.Font.Bold = True
2672
+ sheet.range(range_two_part_3).api.HorizontalAlignment = -4108
2673
+ sheet.range(range_two_part_3).api.VerticalAlignment = -4108
2674
+ sheet.range(f'P2:U3').color = 0x47a100
2675
+
2676
+ # 第三行:列标题
2677
+ range_three = f'A3:U3'
2678
+ sheet.range('A3').value = ['日期', '店铺', '店长', '昨日单量', '对比前日', '昨日销售额', '对比前日', '昨日访客',
2679
+ '对比前天', '备货款A', '对比前日', '新款A', '对比前日', '在售商品', '对比前日', '待上架',
2680
+ '对比前日', '昨日上传', '对比前日', '已售罄', '已下架']
2681
+ sheet.range(range_three).api.Font.Size = 11
2682
+ sheet.range(range_three).api.Font.Color = 0xFFFFFF
2683
+ sheet.range(range_three).api.Font.Bold = True
2684
+ sheet.range(range_three).api.HorizontalAlignment = -4108
2685
+ sheet.range(range_three).api.VerticalAlignment = -4108
2686
+
2687
+ # 第四行:汇总行
2688
+ range_four = f'B4:U4'
2689
+ sheet.range('B4').value = '汇总'
2690
+ sheet.range('C4').value = '-'
2691
+ sheet.range(range_four).api.Font.Size = 11
2692
+ sheet.range(range_four).api.HorizontalAlignment = -4108
2693
+ sheet.range(range_four).api.VerticalAlignment = -4108
2694
+ sheet.range(f'B4:U4').color = 0x50d092
2695
+
2696
+ def _format_monthly_header(self, sheet, las_row):
2697
+ """格式化月度表头,完全按照原始格式"""
2698
+ # 第一行:标题(合并A1:S1)
2699
+ range_one = f'A1:S1'
2700
+ sheet.range(range_one).merge()
2701
+ sheet.range(range_one).api.Font.Size = 24
2702
+ sheet.range(range_one).api.Font.Bold = True
2703
+ sheet.range(range_one).api.HorizontalAlignment = -4108
2704
+ sheet.range(range_one).api.VerticalAlignment = -4108
2705
+
2706
+ # 第二行:分类标题
2707
+ range_two_part_1 = f'A2'
2708
+ range_two_part_2 = f'B2:M2'
2709
+ range_two_part_3 = f'N2:S2'
2710
+ sheet.range(range_two_part_2).merge()
2711
+ sheet.range(range_two_part_3).merge()
2712
+
2713
+ sheet.range(f'A2:A3').color = 0x47a100
2714
+
2715
+ sheet.range('B2').value = '店铺的结果和稳定性'
2716
+ sheet.range(range_two_part_2).api.Font.Size = 16
2717
+ sheet.range(range_two_part_2).api.Font.Color = 0xFFFFFF
2718
+ sheet.range(range_two_part_2).api.Font.Bold = True
2719
+ sheet.range(range_two_part_2).api.HorizontalAlignment = -4108
2720
+ sheet.range(range_two_part_2).api.VerticalAlignment = -4108
2721
+ sheet.range(f'B2:M3').color = 0x0000FF
2722
+
2723
+ sheet.range('N2').value = '上新的质量和数量'
2724
+ sheet.range(range_two_part_3).api.Font.Size = 16
2725
+ sheet.range(range_two_part_3).api.Font.Color = 0xFFFFFF
2726
+ sheet.range(range_two_part_3).api.Font.Bold = True
2727
+ sheet.range(range_two_part_3).api.HorizontalAlignment = -4108
2728
+ sheet.range(range_two_part_3).api.VerticalAlignment = -4108
2729
+ sheet.range(f'N2:S3').color = 0x47a100
2730
+
2731
+ # 第三行:列标题
2732
+ range_three = f'A3:S3'
2733
+ sheet.range('A3').value = ['日期', '昨日单量', '对比前日', '昨日销售额', '对比前日', '昨日访客', '对比前天',
2734
+ '备货款A', '对比前日', '新款A', '对比前日', '在售商品', '对比前日', '待上架',
2735
+ '对比前日', '昨日上传', '对比前日', '已售罄', '已下架']
2736
+ sheet.range(range_three).api.Font.Size = 11
2737
+ sheet.range(range_three).api.Font.Color = 0xFFFFFF
2738
+ sheet.range(range_three).api.Font.Bold = True
2739
+ sheet.range(range_three).api.HorizontalAlignment = -4108
2740
+ sheet.range(range_three).api.VerticalAlignment = -4108
2741
+
2742
+ # 第四行:汇总行
2743
+ range_four = f'A4:S4'
2744
+ sheet.range('A4').value = '汇总'
2745
+ sheet.range(range_four).api.Font.Size = 11
2746
+ sheet.range(range_four).api.HorizontalAlignment = -4108
2747
+ sheet.range(range_four).api.VerticalAlignment = -4108
2748
+ sheet.range(f'A4:S4').color = 0x50d092
2749
+
2750
+ def _set_summary_formulas(self, sheet, las_row):
2751
+ """设置汇总公式"""
2752
+ for col in range(2, 22): # B列到U列(跳过A列日期)
2753
+ col_letter = xw.utils.col_name(col)
2754
+ if col_letter not in ['A', 'B', 'C']: # A列是日期,B列是汇总,C列是-
2755
+ sheet.range(f'{col_letter}4').formula = f'=SUM({col_letter}5:{col_letter}{las_row})'
2756
+ # 所有列水平居中和垂直居中
2757
+ sheet.range(f'{col_letter}:{col_letter}').api.HorizontalAlignment = -4108
2758
+ sheet.range(f'{col_letter}:{col_letter}').api.VerticalAlignment = -4108
2759
+
2760
+ def _set_monthly_summary_formulas(self, sheet, las_row):
2761
+ """设置月度汇总公式"""
2762
+ for col in range(2, 20): # B列到S列(对应原始代码的 2 到 20)
2763
+ col_letter = xw.utils.col_name(col)
2764
+ # 所有列水平居中和垂直居中
2765
+ sheet.range(f'{col_letter}:{col_letter}').api.HorizontalAlignment = -4108
2766
+ sheet.range(f'{col_letter}:{col_letter}').api.VerticalAlignment = -4108
2767
+ # 设置汇总公式(原始代码使用固定的36行)
2768
+ sheet.range(f'{col_letter}4').formula = f'=SUM({col_letter}5:{col_letter}36)'
2769
+
2770
+ def _set_borders(self, sheet, range_str):
2771
+ """设置边框"""
2772
+ range_to_border = sheet.range(range_str)
2773
+ # 设置外部边框
2774
+ range_to_border.api.Borders(7).LineStyle = 1 # 上边框
2775
+ range_to_border.api.Borders(8).LineStyle = 1 # 下边框
2776
+ range_to_border.api.Borders(9).LineStyle = 1 # 左边框
2777
+ range_to_border.api.Borders(10).LineStyle = 1 # 右边框
2778
+ # 设置内部边框
2779
+ range_to_border.api.Borders(1).LineStyle = 1 # 内部上边框
2780
+ range_to_border.api.Borders(2).LineStyle = 1 # 内部下边框
2781
+ range_to_border.api.Borders(3).LineStyle = 1 # 内部左边框
2782
+ range_to_border.api.Borders(4).LineStyle = 1 # 内部右边框
2783
+
2784
+ def format_bak_advice(self, excel_path, sheet_name, mode):
2785
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2786
+ beautify_title(sheet)
2787
+ add_borders(sheet)
2788
+ column_to_left(sheet,
2789
+ ["商品信息", "备货建议", "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率",
2790
+ "自主参与活动"])
2791
+ autofit_column(sheet, ['店铺名称', '商品信息', '备货建议', "近7天SKU销量/SKC销量/SKC曝光",
2792
+ "SKC点击率/SKC转化率",
2793
+ "自主参与活动"])
2794
+
2795
+ if mode in [2, 5, 6, 7, 8, 9, 10]:
2796
+ format_to_number(sheet, ['本地和采购可售天数'], 1)
2797
+ add_formula_for_column(sheet, '本地和采购可售天数', '=IF(H2>0, (F2+G2)/H2,0)')
2798
+ add_formula_for_column(sheet, '建议采购', '=IF(I2 > J2,0,E2)')
2799
+
2800
+ colorize_by_field(sheet, 'SKC')
2801
+ specify_column_width(sheet, ['商品信息'], 180 / 6)
2802
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'])
2803
+ wb.save()
2804
+ close_excel(app, wb)
2805
+ if mode == 4:
2806
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2807
+
2808
+ def write_bak_advice(self, mode_list):
2809
+ excel_path_list = [
2810
+ [1, self.config.Excel_Bak_Advise],
2811
+ [2, self.config.Excel_Purchase_Advise2],
2812
+ [3, self.config.Excel_Product_On_Shelf_Yesterday],
2813
+ [4, f'{self.config.auto_dir}/shein/昨日出单/昨日出单(#len#)_#store_name#_{TimeUtils.today_date()}.xlsx'],
2814
+ [5, self.config.Excel_Purchase_Advise],
2815
+ [6, self.config.Excel_Purchase_Advise6],
2816
+ [7, self.config.Excel_Purchase_Advise7],
2817
+ [8, self.config.Excel_Purchase_Advise8],
2818
+ [9, self.config.Excel_Purchase_Advise9],
2819
+ [10, self.config.Excel_Purchase_Advise10],
2820
+ ]
2821
+ mode_excel_path_list = [row for row in excel_path_list if row[0] in mode_list]
2822
+ new_excel_path_list = []
2823
+ for mode, excel_path in mode_excel_path_list:
2824
+ summary_excel_data = []
2825
+ cache_file = f'{self.config.auto_dir}/shein/cache/bak_advice_{mode}_{TimeUtils.today_date()}.json'
2826
+ dict = read_dict_from_file(cache_file)
2827
+ header = []
2828
+ new_excel_path = excel_path
2829
+ for store_name, excel_data in dict.items():
2830
+ sheet_name = store_name
2831
+ # 处理每个店铺的数据
2832
+
2833
+ if mode in [2, 4]:
2834
+ new_excel_path = str(excel_path).replace('#len#', str(len(excel_data[1:])))
2835
+ new_excel_path = new_excel_path.replace('#store_name#', store_name)
2836
+ new_excel_path_list.append(new_excel_path)
2837
+ sheet_name = 'Sheet1'
2838
+
2839
+ log(new_excel_path)
2840
+ if mode in [2]:
2841
+ excel_data = sort_by_column(excel_data, 4, 1)
2842
+ write_data(new_excel_path, sheet_name, excel_data)
2843
+ self.format_bak_advice(new_excel_path, sheet_name, mode)
2844
+
2845
+ # 是否合并表格数据
2846
+ if mode in [1, 3]:
2847
+ header = excel_data[0]
2848
+ summary_excel_data += excel_data[1:]
2849
+
2850
+ if mode in [1, 3]:
2851
+ sheet_name = 'Sheet1'
2852
+ write_data(new_excel_path, sheet_name, [header] + summary_excel_data)
2853
+ self.format_bak_advice(new_excel_path, sheet_name, mode)
2854
+
2855
+ return new_excel_path_list
2856
+
2857
+ def write_activity_list(self):
2858
+ cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
2859
+ dict_activity = read_dict_from_file(cache_file)
2860
+ all_data = []
2861
+ header = []
2862
+ for store_username, excel_data in dict_activity.items():
2863
+ header = excel_data[:1]
2864
+ all_data += excel_data[1:]
2865
+
2866
+ all_data = header + all_data
2867
+
2868
+ excel_path = create_file_path(self.config.excel_activity_list)
2869
+ sheet_name = 'Sheet1'
2870
+ write_data(excel_path, sheet_name, all_data)
2871
+ self.format_activity_list(excel_path, sheet_name)
2872
+
2873
+ def format_activity_list(self, excel_path, sheet_name):
2874
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2875
+ beautify_title(sheet)
2876
+ add_borders(sheet)
2877
+ column_to_left(sheet, ['活动信息'])
2878
+ colorize_by_field(sheet, '店铺名称')
2879
+ autofit_column(sheet, ['店铺名称', '活动信息'])
2880
+ wb.save()
2881
+ close_excel(app, wb)
2882
+
2883
+ def write_jit_data(self):
2884
+ excel_path_1 = create_file_path(self.config.Excel_Order_Type_1)
2885
+ summary_excel_data_1 = []
2886
+
2887
+ cache_file_1 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_1_{TimeUtils.get_period()}.json'
2888
+ dict_1 = read_dict_from_file(cache_file_1)
2889
+ dict_store = read_dict_from_file(f'{self.config.auto_dir}/shein_store_alias.json')
2890
+
2891
+ header = []
2892
+ for store_username, excel_data in dict_1.items():
2893
+ # store_name = dict_store.get(store_username)
2894
+ # sheet_name = store_name
2895
+ # write_data(excel_path_1, sheet_name, excel_data)
2896
+ # self.format_jit(excel_path_1, sheet_name)
2897
+ header = excel_data[0]
2898
+ summary_excel_data_1 += excel_data[1:]
2899
+
2900
+ if len(summary_excel_data_1) > 0:
2901
+ sheet_name = 'Sheet1'
2902
+ write_data(excel_path_1, sheet_name, [header] + summary_excel_data_1)
2903
+ self.format_jit(excel_path_1, sheet_name)
2904
+
2905
+ excel_path_2 = create_file_path(self.config.Excel_Order_Type_2)
2906
+ summary_excel_data_2 = []
2907
+
2908
+ cache_file_2 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_2_{TimeUtils.get_period()}.json'
2909
+ dict_2 = read_dict_from_file(cache_file_2)
2910
+
2911
+ header = []
2912
+ for store_username, excel_data in dict_2.items():
2913
+ # store_name = dict_store.get(store_username)
2914
+ # sheet_name = store_name
2915
+ # write_data(excel_path_2, sheet_name, excel_data)
2916
+ # self.format_jit(excel_path_2, sheet_name)
2917
+ header = excel_data[0]
2918
+ summary_excel_data_2 += excel_data[1:]
2919
+
2920
+ if len(summary_excel_data_2) > 0:
2921
+ sheet_name = 'Sheet1'
2922
+ write_data(excel_path_2, sheet_name, [header] + summary_excel_data_2)
2923
+ self.format_jit(excel_path_2, sheet_name)
2924
+
2925
+ def format_jit(self, excel_path, sheet_name):
2926
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2927
+ beautify_title(sheet)
2928
+ add_borders(sheet)
2929
+ colorize_by_field(sheet, 'SKC')
2930
+ column_to_left(sheet, ["商品信息", "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
2931
+ autofit_column(sheet,
2932
+ ['店铺名称', '商品信息', "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
2933
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'])
2934
+ wb.save()
2935
+ close_excel(app, wb)
2936
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2937
+
2938
+ def write_week_report(self):
2939
+ excel_path = create_file_path(self.config.excel_week_sales_report)
2940
+ log(excel_path)
2941
+
2942
+ cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
2943
+ dict = read_dict_from_file(cache_file)
2944
+
2945
+ summary_excel_data = []
2946
+ header = []
2947
+ for store_name, excel_data in dict.items():
2948
+ # sheet_name = store_name
2949
+ # write_data(excel_path, sheet_name, excel_data)
2950
+ # self.format_week_report(excel_path, sheet_name)
2951
+ header = excel_data[0]
2952
+ summary_excel_data += excel_data[1:]
2953
+ summary_excel_data = [header] + summary_excel_data
2954
+ sheet_name = 'Sheet1'
2955
+ write_data(excel_path, sheet_name, summary_excel_data)
2956
+ self.format_week_report(excel_path, sheet_name)
2957
+
2958
+ def format_week_report(self, excel_path, sheet_name):
2959
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2960
+ beautify_title(sheet)
2961
+ column_to_left(sheet, ['商品信息'])
2962
+ format_to_money(sheet, ['申报价', '成本价', '毛利润', '利润'])
2963
+ format_to_percent(sheet, ['支付率', '点击率', '毛利率'])
2964
+ self.dealFormula(sheet) # 有空再封装优化
2965
+ colorize_by_field(sheet, 'SPU')
2966
+ autofit_column(sheet, ['商品信息', '店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
2967
+ column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动', '近7天SKU销量/SKC销量/SKC曝光'])
2968
+ specify_column_width(sheet, ['商品标题'], 150 / 6)
2969
+ add_borders(sheet)
2970
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'], 'shein', 120, None, None, True)
2971
+ wb.save()
2972
+ close_excel(app, wb)
2973
+
2974
+ # 处理公式计算
2975
+ def dealFormula(self, sheet):
2976
+ # 增加列 周销增量 月销增量
2977
+ col_week_increment = find_column_by_data(sheet, 1, '周销增量')
2978
+ if col_week_increment is None:
2979
+ col_week_increment = find_column_by_data(sheet, 1, '远30天销量')
2980
+ log(f'{col_week_increment}:{col_week_increment}')
2981
+ sheet.range(f'{col_week_increment}:{col_week_increment}').insert('right')
2982
+ sheet.range(f'{col_week_increment}1').value = '周销增量'
2983
+ log('已增加列 周销增量')
2984
+
2985
+ col_month_increment = find_column_by_data(sheet, 1, '月销增量')
2986
+ if col_month_increment is None:
2987
+ col_month_increment = find_column_by_data(sheet, 1, '总销量')
2988
+ log(f'{col_month_increment}:{col_month_increment}')
2989
+ sheet.range(f'{col_month_increment}:{col_month_increment}').insert('right')
2990
+ sheet.range(f'{col_month_increment}1').value = '月销增量'
2991
+ log('已增加列 月销增量')
2992
+
2993
+ col_month_profit = find_column_by_data(sheet, 1, '近30天利润')
2994
+ if col_month_profit is None:
2995
+ col_month_profit = find_column_by_data(sheet, 1, '总利润')
2996
+ sheet.range(f'{col_month_profit}:{col_month_profit}').insert('right')
2997
+ log((f'{col_month_profit}:{col_month_profit}'))
2998
+ sheet.range(f'{col_month_profit}1').value = '近30天利润'
2999
+ log('已增加列 近30天利润')
3000
+
3001
+ col_week_profit = find_column_by_data(sheet, 1, '近7天利润')
3002
+ if col_week_profit is None:
3003
+ col_week_profit = find_column_by_data(sheet, 1, '近30天利润')
3004
+ sheet.range(f'{col_week_profit}:{col_week_profit}').insert('right')
3005
+ log((f'{col_week_profit}:{col_week_profit}'))
3006
+ sheet.range(f'{col_week_profit}1').value = '近7天利润'
3007
+ log('已增加列 近7天利润')
3008
+
3009
+ # return
3010
+
3011
+ # 查找 申报价,成本价,毛利润,毛利润率 所在列
3012
+ col_verify_price = find_column_by_data(sheet, 1, '申报价')
3013
+ col_cost_price = find_column_by_data(sheet, 1, '成本价')
3014
+ col_gross_profit = find_column_by_data(sheet, 1, '毛利润')
3015
+ col_gross_margin = find_column_by_data(sheet, 1, '毛利率')
3016
+
3017
+ col_week_1 = find_column_by_data(sheet, 1, '近7天销量')
3018
+ col_week_2 = find_column_by_data(sheet, 1, '远7天销量')
3019
+ col_month_1 = find_column_by_data(sheet, 1, '近30天销量')
3020
+ col_month_2 = find_column_by_data(sheet, 1, '远30天销量')
3021
+
3022
+ # 遍历可用行
3023
+ used_range_row = sheet.range('A1').expand('down')
3024
+ for i, cell in enumerate(used_range_row):
3025
+ row = i + 1
3026
+ if row < 2:
3027
+ continue
3028
+ rangeA = f'{col_verify_price}{row}'
3029
+ rangeB = f'{col_cost_price}{row}'
3030
+
3031
+ rangeC = f'{col_week_increment}{row}'
3032
+ rangeD = f'{col_month_increment}{row}'
3033
+
3034
+ # rangeE = f'{col_total_profit}{row}'
3035
+ rangeF = f'{col_month_profit}{row}'
3036
+ rangeG = f'{col_week_profit}{row}'
3037
+
3038
+ # 设置毛利润和毛利润率列公式与格式
3039
+ sheet.range(f'{col_gross_profit}{row}').formula = f'=IF(ISNUMBER({rangeB}),{rangeA}-{rangeB},"")'
3040
+ sheet.range(f'{col_gross_profit}{row}').number_format = '0.00'
3041
+ sheet.range(f'{col_gross_margin}{row}').formula = f'=IF(ISNUMBER({rangeB}),({rangeA}-{rangeB})/{rangeA},"")'
3042
+ sheet.range(f'{col_gross_margin}{row}').number_format = '0.00%'
3043
+
3044
+ sheet.range(rangeC).formula = f'={col_week_1}{row}-{col_week_2}{row}'
3045
+ sheet.range(rangeC).number_format = '0'
3046
+ sheet.range(rangeD).formula = f'={col_month_1}{row}-{col_month_2}{row}'
3047
+ sheet.range(rangeD).number_format = '0'
3048
+
3049
+ # sheet.range(rangeE).formula = f'=IF(ISNUMBER({rangeB}),{col_total}{row}*{col_gross_profit}{row},"")'
3050
+ # sheet.range(rangeE).number_format = '0.00'
3051
+ sheet.range(rangeF).formula = f'=IF(ISNUMBER({rangeB}),{col_month_1}{row}*{col_gross_profit}{row},"")'
3052
+ sheet.range(rangeF).number_format = '0.00'
3053
+ sheet.range(rangeG).formula = f'=IF(ISNUMBER({rangeB}),{col_week_1}{row}*{col_gross_profit}{row},"")'
3054
+ sheet.range(rangeG).number_format = '0.00'
3055
+
3056
+ def write_check_order(self, erp, start_date, end_date):
3057
+ header = ['店铺账号', '店铺别名', '店长', '报账单号', '货号', 'SKC', '平台SKU', '商家SKU', '属性集', '商品数量', '账单类型', '收支类型', '状态', '币种', '金额', 'ERP成本',
3058
+ '成本总额', '业务单号', '费用类型', '备注', '来源单号', '账单创建时间', '台账添加时间', '报账时间', '预计结算日期', '实际结算日期']
3059
+ excel_data = [header]
3060
+
3061
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
3062
+
3063
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
3064
+ dict = read_dict_from_file(cache_file)
3065
+ for store_username, data_list in dict.items():
3066
+ for item in data_list:
3067
+ store_name = dict_store.get(store_username)
3068
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
3069
+
3070
+ row_item = []
3071
+ row_item.append(store_username)
3072
+ row_item.append(store_name)
3073
+ row_item.append(store_manager)
3074
+ row_item.append(item['reportOrderNo'])
3075
+ row_item.append(item['goodsSn'])
3076
+ row_item.append(item['skcName'])
3077
+ row_item.append(item['skuCode'])
3078
+ row_item.append(item['skuSn'])
3079
+ row_item.append(item['suffix'])
3080
+ row_item.append(item['goodsCount'])
3081
+ row_item.append(item['secondOrderTypeName'])
3082
+ row_item.append(item['inAndOutName'])
3083
+ row_item.append(item['settlementStatusName'])
3084
+ row_item.append(item['settleCurrencyCode'])
3085
+ row_item.append(item['income'])
3086
+ row_item.append(self.bridge.get_sku_cost(item['skuSn'], erp))
3087
+ row_item.append('')
3088
+ row_item.append(item['bzOrderNo'])
3089
+ row_item.append(item['expenseTypeName'])
3090
+ row_item.append(item['remark'])
3091
+ row_item.append(item['sourceNo'])
3092
+ row_item.append(item['addTime'])
3093
+ row_item.append(item['businessCompletedTime'])
3094
+ row_item.append(item['reportTime'])
3095
+ row_item.append(item['estimatePayTime'])
3096
+ row_item.append(item['completedPayTime'])
3097
+
3098
+ excel_data.append(row_item)
3099
+
3100
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
3101
+ write_dict_to_file(cache_file_excel, excel_data)
3102
+
3103
+ sheet_name = '收支明细'
3104
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
3105
+ (sheet_name, 'write', excel_data, ['R']),
3106
+ (sheet_name, 'format', self.format_check_order)
3107
+ ])
3108
+
3109
+ header = ['店铺账号', '店铺别名', '店长', '出库金额', '出库成本', '备货作业费', '代收服务费', '订单履约服务费', '订单退货', '退货处理费', '退货单履约服务费', '利润']
3110
+ excel_data = [header]
3111
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
3112
+ dict = read_dict_from_file(cache_file)
3113
+ for store_username, data_list in dict.items():
3114
+ store_name = dict_store.get(store_username)
3115
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
3116
+ row_item = []
3117
+ row_item.append(store_username)
3118
+ row_item.append(store_name)
3119
+ row_item.append(store_manager)
3120
+ row_item.append('')
3121
+ row_item.append('')
3122
+ row_item.append('')
3123
+ row_item.append('')
3124
+ row_item.append('')
3125
+ row_item.append('')
3126
+ row_item.append('')
3127
+ row_item.append('')
3128
+ row_item.append('')
3129
+ excel_data.append(row_item)
3130
+
3131
+ sheet_name = '总表'
3132
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
3133
+ (sheet_name, 'write', excel_data),
3134
+ (sheet_name, 'format', self.format_check_order),
3135
+ ('Sheet1', 'delete'),
3136
+ (sheet_name, 'move', 1),
3137
+ ])
3138
+
3139
+ def merge_finance_details_to_summary(self, source_dir, start_date, end_date, output_dir=None):
3140
+ """
3141
+ 将多个店铺的财务收支明细Excel文件合并到一个汇总Excel中
3142
+
3143
+ Args:
3144
+ source_dir: 源文件目录路径
3145
+ start_date: 开始日期,格式: YYYY-MM-DD
3146
+ end_date: 结束日期,格式: YYYY-MM-DD
3147
+ output_dir: 输出目录,默认为None则使用source_dir
3148
+
3149
+ Returns:
3150
+ 汇总Excel文件路径
3151
+ """
3152
+ import os
3153
+ import glob
3154
+
3155
+ # 确定输出目录
3156
+ if output_dir is None:
3157
+ output_dir = source_dir
3158
+ os.makedirs(output_dir, exist_ok=True)
3159
+
3160
+ # 提取月份(从start_date中提取)
3161
+ from datetime import datetime
3162
+ start_dt = datetime.strptime(start_date, '%Y-%m-%d')
3163
+ month_str = f"{start_dt.month}月"
3164
+
3165
+ # 生成输出文件名
3166
+ output_filename = f'希音财务月报POP-{month_str}.xlsx'
3167
+ output_path = os.path.join(output_dir, output_filename)
3168
+
3169
+ log(f'开始合并财务收支明细: {source_dir}')
3170
+
3171
+ # 查找所有匹配的Excel文件
3172
+ pattern = os.path.join(source_dir, f'finance_details_*_{start_date}_{end_date}.xlsx')
3173
+ excel_files = glob.glob(pattern)
3174
+
3175
+ if len(excel_files) == 0:
3176
+ log(f'未找到匹配的文件: {pattern}')
3177
+ raise Exception(f'未找到匹配的财务收支明细文件')
3178
+
3179
+ log(f'找到 {len(excel_files)} 个Excel文件待合并')
3180
+
3181
+ # 读取所有Excel文件并合并数据
3182
+ all_detail_data = []
3183
+ store_list = [] # 存储店铺账号列表
3184
+ header = None
3185
+
3186
+ for idx, excel_file in enumerate(excel_files):
3187
+ log(f'读取文件 {idx + 1}/{len(excel_files)}: {os.path.basename(excel_file)}')
3188
+
3189
+ # 从文件名中提取店铺账号
3190
+ filename = os.path.basename(excel_file)
3191
+ # 格式: finance_details_{store_username}_{start_date}_{end_date}.xlsx
3192
+ parts = filename.replace('.xlsx', '').split('_')
3193
+ if len(parts) >= 5:
3194
+ store_username = parts[2] # finance_details_{store_username}_...
3195
+ store_list.append(store_username)
3196
+ else:
3197
+ log(f'警告:无法从文件名提取店铺账号: {filename}')
3198
+ store_username = 'unknown'
3199
+ store_list.append(store_username)
3200
+
3201
+ # 读取Excel文件
3202
+ try:
3203
+ df = pd.read_excel(excel_file, sheet_name=0)
3204
+
3205
+ if idx == 0:
3206
+ # 第一个文件,保存表头
3207
+ header = df.columns.tolist()
3208
+ log(f'表头列数: {len(header)}')
3209
+
3210
+ # 读取数据(跳过表头)
3211
+ data_rows = df.values.tolist()
3212
+ log(f'读取到 {len(data_rows)} 行数据')
3213
+
3214
+ # 添加到总数据中
3215
+ all_detail_data.extend(data_rows)
3216
+
3217
+ except Exception as e:
3218
+ log(f'读取文件失败: {excel_file}, 错误: {str(e)}')
3219
+ continue
3220
+
3221
+ log(f'合并完成,总共 {len(all_detail_data)} 行数据')
3222
+
3223
+ dict_store_manager_shein = self.config.shein_store_manager
3224
+ dict_store_name = read_dict_from_file(self.config.shein_store_alias)
3225
+
3226
+ # 在"金额"列后面添加"ERP成本"和"成本总额"两列
3227
+ if header:
3228
+ # 查找"金额"列和"商家SKU"列的位置
3229
+ amount_col_idx = None
3230
+ sku_col_idx = None
3231
+ business_no_col_idx = None # 业务单号列索引
3232
+
3233
+ for i, col_name in enumerate(header):
3234
+ if '金额' in str(col_name):
3235
+ amount_col_idx = i
3236
+ if '商家SKU' in str(col_name) or 'SKU' in str(col_name):
3237
+ sku_col_idx = i
3238
+ if '店铺名称' in str(col_name):
3239
+ store_name_idx = i
3240
+ if '店长' in str(col_name):
3241
+ store_manager_idx = i
3242
+ if '业务单号' in str(col_name):
3243
+ business_no_col_idx = i
3244
+
3245
+ if amount_col_idx is not None:
3246
+ # 在"金额"列后面插入两列
3247
+ new_header = header[:amount_col_idx + 1] + ['ERP成本', '成本总额'] + header[amount_col_idx + 1:]
3248
+ log(f'在第{amount_col_idx + 1}列(金额)后面插入"ERP成本"和"成本总额"两列')
3249
+
3250
+ # 业务单号列在插入新列后的索引需要调整
3251
+ if business_no_col_idx is not None and business_no_col_idx > amount_col_idx:
3252
+ business_no_col_idx_adjusted = business_no_col_idx + 2 # 因为插入了2列
3253
+ else:
3254
+ business_no_col_idx_adjusted = business_no_col_idx
3255
+
3256
+ # 处理数据行:在相应位置插入ERP成本和成本总额
3257
+ new_data = []
3258
+ for row_idx, row in enumerate(all_detail_data):
3259
+ # 转换为list(如果是tuple)
3260
+ row_list = list(row)
3261
+
3262
+ store_username = row_list[0]
3263
+ store_name = dict_store_name.get(store_username)
3264
+ store_manager = dict_store_manager_shein.get(str(store_username).lower(), '-')
3265
+ row_list[1] = store_name
3266
+ row_list[2] = store_manager
3267
+
3268
+ # 获取商家SKU
3269
+ sku = None
3270
+ if sku_col_idx is not None and sku_col_idx < len(row_list):
3271
+ sku = row_list[sku_col_idx]
3272
+
3273
+ # 获取ERP成本
3274
+ erp_cost = ''
3275
+ if sku and self.bridge:
3276
+ try:
3277
+ erp_cost = self.bridge.get_sku_cost(str(sku), self.config.erp_source)
3278
+ except Exception as e:
3279
+ log(f'获取SKU成本失败: {sku}, 错误: {str(e)}')
3280
+
3281
+ # 在"金额"列后面插入两列数据
3282
+ new_row = row_list[:amount_col_idx + 1] + [erp_cost, ''] + row_list[amount_col_idx + 1:]
3283
+ new_data.append(new_row)
3284
+
3285
+ # 每1000行输出一次进度
3286
+ if (row_idx + 1) % 1000 == 0:
3287
+ log(f'处理进度: {row_idx + 1}/{len(all_detail_data)}')
3288
+
3289
+ # 更新表头和数据
3290
+ header = new_header
3291
+ all_detail_data = new_data
3292
+ log(f'ERP成本数据处理完成,新表头列数: {len(header)}')
3293
+ if business_no_col_idx_adjusted is not None:
3294
+ log(f'业务单号列已转换为字符串格式,列索引: {business_no_col_idx_adjusted}')
3295
+ else:
3296
+ log('警告:未找到"金额"列,无法添加ERP成本和成本总额列')
3297
+
3298
+ # 准备汇总表数据
3299
+ summary_header = ['店铺账号', '店铺别名', '店长', '出库金额', '出库成本', '备货作业费',
3300
+ '代收服务费', '订单履约服务费', '订单退货', '退货处理费', '退货单履约服务费', '利润']
3301
+ summary_data = [summary_header]
3302
+
3303
+ # 为每个店铺创建一行(其他字段留空,由公式计算)
3304
+ for store_username in store_list:
3305
+ store_name = dict_store_name.get(store_username)
3306
+ store_manager = dict_store_manager_shein.get(str(store_username).lower(), '-')
3307
+ row = [store_username, store_name, store_manager, '', '', '', '', '', '', '', '', '']
3308
+ summary_data.append(row)
3309
+
3310
+ # 写入Excel文件
3311
+ log(f'开始写入汇总Excel: {output_path}')
3312
+
3313
+ # 查找需要格式化为文本的列
3314
+ text_format_columns = []
3315
+ str_keywords = ['业务单号']
3316
+
3317
+ def col_idx_to_letter(idx):
3318
+ """将列索引转换为Excel列字母 (0->A, 1->B, ..., 25->Z, 26->AA, ...)"""
3319
+ result = ''
3320
+ idx += 1 # Excel列从1开始
3321
+ while idx > 0:
3322
+ idx -= 1
3323
+ result = chr(65 + idx % 26) + result
3324
+ idx //= 26
3325
+ return result
3326
+
3327
+ for col_idx, col_name in enumerate(header):
3328
+ col_name_str = str(col_name)
3329
+ # 检查列名是否包含需要保持为文本的关键词
3330
+ if any(keyword in col_name_str for keyword in str_keywords):
3331
+ col_letter = col_idx_to_letter(col_idx)
3332
+ text_format_columns.append(col_letter)
3333
+ log(f'列"{col_name}"(第{col_idx}列,Excel列{col_letter})将格式化为文本')
3334
+
3335
+ log(f'共{len(text_format_columns)}列需要格式化为文本: {text_format_columns}')
3336
+
3337
+ # 使用batch_excel_operations批量写入和格式化
3338
+ operations = [
3339
+ ('财务收支明细', 'write', [header] + all_detail_data, text_format_columns),
3340
+ ('财务收支明细', 'format', self.format_check_order),
3341
+ ('总表', 'write', summary_data),
3342
+ ('总表', 'format', self.format_check_order),
3343
+ ('Sheet1', 'delete'),
3344
+ ('总表', 'move', 1),
3345
+ ]
3346
+
3347
+ batch_excel_operations(output_path, operations)
3348
+
3349
+ log(f'合并完成,文件已保存: {output_path}')
3350
+ return output_path
3351
+
3352
+ def format_check_order(self, sheet):
3353
+ if sheet.name == '收支明细' or sheet.name == '财务收支明细':
3354
+ beautify_title(sheet)
3355
+ add_borders(sheet)
3356
+ format_to_datetime(sheet, ['时间'])
3357
+ format_to_date(sheet, ['日期'])
3358
+ format_to_money(sheet, ['金额', '成本', 'ERP成本', '成本总额'])
3359
+ column_to_right(sheet, ['金额', '成本', 'ERP成本', '成本总额'])
3360
+ column_to_left(sheet, ['货号', '商家SKU'])
3361
+
3362
+ # 将业务单号列格式化为文本格式
3363
+ try:
3364
+ from .fun_excel import find_column_by_data
3365
+ business_no_col = find_column_by_data(sheet, 1, '业务单号')
3366
+ if business_no_col:
3367
+ # 设置整列为文本格式
3368
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
3369
+ sheet.range(f'{business_no_col}2:{business_no_col}{last_row}').number_format = '@'
3370
+ log(f'业务单号列({business_no_col})已设置为文本格式')
3371
+ except Exception as e:
3372
+ log(f'设置业务单号列格式失败: {str(e)}')
3373
+
3374
+ # 为成本总额列添加公式
3375
+ # 成本总额 = 数量 * ERP成本
3376
+ try:
3377
+ # 查找"数量"列和"ERP成本"列的位置
3378
+ from .fun_excel import find_column_by_data
3379
+ quantity_col = find_column_by_data(sheet, 1, '商品数量')
3380
+ erp_cost_col = find_column_by_data(sheet, 1, 'ERP成本')
3381
+
3382
+ log("数量,ERP成本", quantity_col, erp_cost_col)
3383
+ if quantity_col and erp_cost_col:
3384
+ # 添加公式:成本总额 = 数量 * ERP成本
3385
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER({erp_cost_col}2),{quantity_col}2*{erp_cost_col}2,"-")')
3386
+ log('成本总额公式已添加')
3387
+ except Exception as e:
3388
+ log(f'添加成本总额公式失败: {str(e)}')
3389
+
3390
+ if sheet.name == '总表':
3391
+ beautify_title(sheet)
3392
+ add_borders(sheet)
3393
+ format_to_money(sheet, ['金额', '成本', '费', '订单退货', '利润'])
3394
+ column_to_right(sheet, ['金额', '成本', '费', '订单退货', '利润'])
3395
+
3396
+ # 使用财务收支明细sheet的引用
3397
+ detail_sheet = '财务收支明细' if '财务收支明细' in [s.name for s in sheet.book.sheets] else '收支明细'
3398
+
3399
+ # 查找财务收支明细sheet中各列的位置
3400
+ from .fun_excel import find_column_by_data
3401
+ detail_ws = None
3402
+ for ws in sheet.book.sheets:
3403
+ if ws.name == detail_sheet:
3404
+ detail_ws = ws
3405
+ break
3406
+
3407
+ if detail_ws:
3408
+ # 查找关键列的位置
3409
+ amount_col = find_column_by_data(detail_ws, 1, '金额') # 金额列
3410
+ cost_total_col = find_column_by_data(detail_ws, 1, '成本总额') # 成本总额列
3411
+ store_col = find_column_by_data(detail_ws, 1, '店铺账号') # 店铺账号列
3412
+ type_col = find_column_by_data(detail_ws, 1, '收支类型') # 收支类型列
3413
+ bill_type_col = find_column_by_data(detail_ws, 1, '账单类型') # 账单类型列
3414
+
3415
+ log(f'找到列位置: 金额={amount_col}, 成本总额={cost_total_col}, 店铺账号={store_col}, 收支类型={type_col}, 账单类型={bill_type_col}')
3416
+
3417
+ # 使用找到的列位置生成公式
3418
+ if amount_col and store_col and type_col:
3419
+ 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},"收入")')
3420
+
3421
+ if cost_total_col and store_col and type_col:
3422
+ # 使用成本总额列
3423
+ add_formula_for_column(sheet, '出库成本', f'=SUMIFS({detail_sheet}!{cost_total_col}:{cost_total_col},{detail_sheet}!{store_col}:{store_col},A2,{detail_sheet}!{type_col}:{type_col},"收入")')
3424
+
3425
+ if amount_col and store_col and type_col and bill_type_col:
3426
+ 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},"备货作业费")')
3427
+ 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},"代收服务费")')
3428
+ 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},"订单履约服务费")')
3429
+ 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},"订单退货")')
3430
+ 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},"退货处理费")')
3431
+ 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},"退货单履约服务费")')
3432
+
3433
+ add_formula_for_column(sheet, '利润', '=D2-E2-F2-G2-H2-I2-J2-K2')
3434
+
3435
+ def write_vssv_order_list(self):
3436
+ """
3437
+ 写入VSSV增值服务订单列表到Excel
3438
+ """
3439
+ # 获取上个月的时间范围
3440
+ first_day, last_day = TimeUtils.get_last_month_range()
3441
+ last_month = TimeUtils.get_last_month()
3442
+
3443
+ # 读取店铺别名映射
3444
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
3445
+
3446
+ # 准备Excel数据
3447
+ header = ['店铺账号', '店铺名称', '增值服务订单号', '增值服务单号', '采购订单号',
3448
+ '平台SKC', '商家SKC', 'SKC数量', '扣款单号', '订单状态', '实际总金额',
3449
+ '增值服务项', '创建时间', '完成时间']
3450
+ excel_data = [header]
3451
+
3452
+ # 遍历vssv_order目录下的所有店铺数据
3453
+ src_directory = f'{self.config.auto_dir}/shein/vssv_order'
3454
+
3455
+ if not os.path.exists(src_directory):
3456
+ log(f'VSSV订单目录不存在: {src_directory}')
3457
+ return
3458
+
3459
+ for entry in os.listdir(src_directory):
3460
+ # 检查是否为匹配的缓存文件
3461
+ if entry.startswith(f"vssv_order_list_") and entry.endswith(f"_{first_day}_{last_day}.json"):
3462
+ file_path = os.path.join(src_directory, entry)
3463
+
3464
+ # 从文件名中提取店铺账号
3465
+ # 格式: vssv_order_list_{store_username}_{first_day}_{last_day}.json
3466
+ parts = entry.replace('.json', '').split('_')
3467
+ if len(parts) >= 5:
3468
+ # vssv_order_list_{store_username}_{first_day}_{last_day}
3469
+ # parts[0]='vssv', parts[1]='order', parts[2]='list', parts[3]=store_username
3470
+ store_username = parts[3]
3471
+ else:
3472
+ log(f'无法解析店铺账号: {entry}')
3473
+ continue
3474
+
3475
+ # 获取店铺名称
3476
+ store_name = dict_store.get(store_username, store_username)
3477
+
3478
+ # 读取订单数据
3479
+ order_list = read_dict_from_file(file_path)
3480
+ log(f'读取店铺 {store_name}({store_username}) 的VSSV订单: {len(order_list)}条')
3481
+
3482
+ # 处理每条订单数据
3483
+ for order in order_list:
3484
+ # 基础订单信息
3485
+ order_no = order.get('orderNo', '-')
3486
+ sub_order_no = order.get('subOrderNo', '-')
3487
+ purchase_no = order.get('purchaseNo', '-')
3488
+ skc_img_path = order.get('skcImgPath', '')
3489
+ skc = order.get('skc', '-')
3490
+ supplier_product_number = order.get('supplierProductNumber', '-')
3491
+ skc_num = order.get('skcNum', 0)
3492
+ order_state_name = order.get('orderStateName', '-')
3493
+ actual_total_amount = order.get('actualTotalAmount', 0)
3494
+
3495
+ # 提取扣款单号(从vendorRepairList数组中)
3496
+ vendor_repair_no = '-'
3497
+ vendor_repair_list = order.get('vendorRepairList', [])
3498
+ if vendor_repair_list and len(vendor_repair_list) > 0:
3499
+ vendor_repair_no = vendor_repair_list[0].get('vendorRepairNo', '-')
3500
+
3501
+ # 提取创建时间和完成时间(从orderChangeLogVo中)
3502
+ create_time = '-'
3503
+ finish_time = '-'
3504
+ order_change_log = order.get('orderChangeLogVo', [])
3505
+ for log_item in order_change_log:
3506
+ if log_item.get('operateType') == 12: # 创建时间
3507
+ create_time = log_item.get('operateTime', '-')
3508
+ elif log_item.get('operateType') == 4: # 增值订单完成时间
3509
+ finish_time = log_item.get('operateTime', '-')
3510
+
3511
+ # 获取增值服务项列表并合并成一个字符串
3512
+ service_items = order.get('subOrderServiceItemVoList', [])
3513
+ service_items_text = '-'
3514
+
3515
+ if service_items:
3516
+ # 将所有服务项合并成一个字符串,每个服务项一行
3517
+ service_lines = []
3518
+ for service_item in service_items:
3519
+ service_name = service_item.get('serviceItemName', '-')
3520
+ settlement_qty = service_item.get('settlementQuantity', 0)
3521
+ item_amount = service_item.get('itemTotalAmount', 0)
3522
+ price = service_item.get('price', 0)
3523
+ # 格式:服务项名称 | 数量 | 金额
3524
+ service_line = f"{service_name}: {settlement_qty}x{price}=¥{item_amount}"
3525
+ service_lines.append(service_line)
3526
+ service_items_text = '\n'.join(service_lines)
3527
+
3528
+ # 添加一行数据
3529
+ row_item = []
3530
+ row_item.append(store_username) # 店铺账号
3531
+ row_item.append(store_name) # 店铺名称
3532
+ row_item.append(order_no) # 增值服务订单号
3533
+ row_item.append(sub_order_no) # 增值服务单号
3534
+ row_item.append(purchase_no) # 采购订单号
3535
+ # row_item.append(skc_img_path) # SKC图片
3536
+ row_item.append(skc) # 平台SKC
3537
+ row_item.append(supplier_product_number) # 商家SKC
3538
+ row_item.append(skc_num) # SKC数量
3539
+ row_item.append(vendor_repair_no) # 扣款单号
3540
+ row_item.append(order_state_name) # 订单状态
3541
+ row_item.append(actual_total_amount) # 实际总金额
3542
+ row_item.append(service_items_text) # 增值服务项(合并)
3543
+ row_item.append(create_time) # 创建时间
3544
+ row_item.append(finish_time) # 完成时间
3545
+ excel_data.append(row_item)
3546
+
3547
+ log(f'共收集到 {len(excel_data) - 1} 条VSSV订单数据')
3548
+
3549
+ # 如果没有数据,只有表头,则不生成Excel
3550
+ if len(excel_data) <= 1:
3551
+ log('没有VSSV订单数据,跳过Excel生成')
3552
+ return
3553
+
3554
+ # 写入Excel
3555
+ excel_path = self.config.excel_path
3556
+ sheet_name = f'{last_month}月增值服务列表'
3557
+
3558
+ batch_excel_operations(excel_path, [
3559
+ (sheet_name, 'write', excel_data, ['C', 'D', 'E', 'I']), # 订单号、单号、采购单号、扣款单号格式化为文本
3560
+ (sheet_name, 'format', self.format_vssv_order_list),
3561
+ ('Sheet1', 'delete')
3562
+ ])
3563
+
3564
+ log(f'VSSV订单列表已写入: {excel_path}')
3565
+
3566
+ def format_vssv_order_list(self, sheet):
3567
+ """
3568
+ 格式化VSSV订单列表Excel
3569
+ """
3570
+ beautify_title(sheet)
3571
+ add_borders(sheet)
3572
+ format_to_money(sheet, ['金额', '总金额'])
3573
+ column_to_right(sheet, ['金额', '数量', '总金额'])
3574
+ format_to_datetime(sheet, ['时间'])
3575
+ column_to_left(sheet, ['店铺账号', '订单号', '单号', 'SKC', '增值服务项'])
3576
+ wrap_column(sheet, ['增值服务项']) # 增值服务项列自动换行
3577
+ autofit_column(sheet, ['店铺名称', '订单状态'])
3578
+ specify_column_width(sheet, ['增值服务订单号', '增值服务单号', '采购订单号', '扣款单号'], 160 / 6)
3579
+ specify_column_width(sheet, ['增值服务项'], 280 / 6) # 服务项列设置较宽
3580
+
3581
+ # 插入SKC图片
3582
+ # InsertImageV2(sheet, ['SKC图片'], 'shein', 90)
3583
+
3584
+ sheet.autofit()
3585
+
3586
+ def write_ledger_month_summary(self, start_date, end_date):
3587
+ """
3588
+ 导出店铺月度出库汇总数据到Excel
3589
+
3590
+ Args:
3591
+ start_date (str): 开始日期(格式:YYYY-MM-DD)
3592
+ end_date (str): 结束日期(格式:YYYY-MM-DD)
3593
+ """
3594
+ from .mysql_module.shein_ledger_month_report_model import SheinLedgerMonthReportManager
3595
+
3596
+ # 获取数据库连接并查询数据
3597
+ manager = SheinLedgerMonthReportManager(self.config.db.database_url)
3598
+ store_data = manager.get_store_month_summary(start_date, end_date)
3599
+
3600
+ # 构建表头(16列)
3601
+ # 店铺名称 | 店铺账号 | 店长 | 1月 | 2月 | ... | 12月 | 汇总
3602
+ header_row = ['店铺名称', '店铺账号', '店长']
3603
+ for month in range(1, 13):
3604
+ header_row.append(f'{month}月')
3605
+ header_row.append('汇总')
3606
+
3607
+ # ========== 构建数量数据 ==========
3608
+ cnt_data = [header_row.copy(), ['合计', '', ''] + [''] * 13]
3609
+
3610
+ # 添加数据行(数量),按年度汇总数量降序排序
3611
+ store_rows = []
3612
+ for store_name, store_info in store_data.items():
3613
+ row = [
3614
+ store_name,
3615
+ store_info['store_username'],
3616
+ store_info['store_manager']
3617
+ ]
3618
+ year_total_cnt = 0
3619
+
3620
+ for month in range(1, 13):
3621
+ month_cnt = store_info['months'].get(month, {'cnt': 0})['cnt']
3622
+ row.append(month_cnt)
3623
+ year_total_cnt += month_cnt
3624
+
3625
+ # 汇总列留空,稍后用公式填充
3626
+ row.append('')
3627
+ store_rows.append((year_total_cnt, row))
3628
+
3629
+ # 按汇总数量降序排序
3630
+ store_rows.sort(key=lambda x: x[0], reverse=True)
3631
+
3632
+ # 添加到数据中
3633
+ for _, row in store_rows:
3634
+ cnt_data.append(row)
3635
+
3636
+ # ========== 构建金额数据 ==========
3637
+ amount_data = [header_row.copy(), ['合计', '', ''] + [''] * 13]
3638
+
3639
+ # 添加数据行(金额),按年度汇总金额降序排序
3640
+ store_rows = []
3641
+ for store_name, store_info in store_data.items():
3642
+ row = [
3643
+ store_name,
3644
+ store_info['store_username'],
3645
+ store_info['store_manager']
3646
+ ]
3647
+ year_total_amount = 0
3648
+
3649
+ for month in range(1, 13):
3650
+ month_amount = store_info['months'].get(month, {'amount': 0})['amount']
3651
+ row.append(month_amount)
3652
+ year_total_amount += month_amount
3653
+
3654
+ # 汇总列留空,稍后用公式填充
3655
+ row.append('')
3656
+ store_rows.append((year_total_amount, row))
3657
+
3658
+ # 按汇总金额降序排序
3659
+ store_rows.sort(key=lambda x: x[0], reverse=True)
3660
+
3661
+ # 添加到数据中
3662
+ for _, row in store_rows:
3663
+ amount_data.append(row)
3664
+
3665
+ # 写入Excel
3666
+ excel_path = self.config.excel_ledger_record
3667
+
3668
+ # 使用batch_excel_operations来创建两个sheet并删除Sheet1
3669
+ operations = [
3670
+ ['总出库数量', 'write', cnt_data],
3671
+ ['总出库数量', 'format', self._format_ledger_cnt_sheet],
3672
+ ['总出库金额', 'write', amount_data],
3673
+ ['总出库金额', 'format', self._format_ledger_amount_sheet],
3674
+ ['Sheet1', 'delete'],
3675
+ ['总出库金额', 'move', 1],
3676
+ ]
3677
+
3678
+ batch_excel_operations(excel_path, operations)
3679
+
3680
+ log(f'月度出库汇总数据已导出到: {excel_path}')
3681
+ log(f'共导出 {len(store_data)} 个店铺的数据,包含2个sheet页(总出库数量、总出库金额)')
3682
+
3683
+ def _format_ledger_cnt_sheet(self, sheet):
3684
+ """
3685
+ 格式化总出库数量sheet
3686
+ """
3687
+ beautify_title(sheet)
3688
+ add_borders(sheet)
3689
+
3690
+ # 为每一行的汇总列添加求和公式(求和D到O列,即12个月)
3691
+ # 因为增加了店铺账号和店长两列,月份数据从D列开始
3692
+ add_formula_for_column(sheet, '汇总', '=SUM(D3:O3)', 3)
3693
+
3694
+ # 为合计行的每一列添加求和公式(从第3行开始求和到最后)
3695
+ add_sum_for_cell(sheet, ['1月', '2月', '3月', '4月', '5月', '6月',
3696
+ '7月', '8月', '9月', '10月', '11月', '12月', '汇总'])
3697
+
3698
+ column_to_left(sheet, ['店铺名称', '店铺账号', '店长'])
3699
+ column_to_right(sheet, ['月', '汇总'])
3700
+ sheet.autofit()
3701
+
3702
+ def _format_ledger_amount_sheet(self, sheet):
3703
+ """
3704
+ 格式化总出库金额sheet
3705
+ """
3706
+ beautify_title(sheet)
3707
+ add_borders(sheet)
3708
+
3709
+ # 为每一行的汇总列添加求和公式(求和D到O列,即12个月)
3710
+ # 因为增加了店铺账号和店长两列,月份数据从D列开始
3711
+ add_formula_for_column(sheet, '汇总', '=SUM(D3:O3)', 3)
3712
+
3713
+ # 为合计行的每一列添加求和公式(从第3行开始求和到最后)
3714
+ add_sum_for_cell(sheet, ['1月', '2月', '3月', '4月', '5月', '6月',
3715
+ '7月', '8月', '9月', '10月', '11月', '12月', '汇总'])
3716
+
3717
+ format_to_money(sheet, ['月', '汇总'])
3718
+ column_to_left(sheet, ['店铺名称', '店铺账号', '店长'])
3719
+ column_to_right(sheet, ['月', '汇总'])
3720
+ sheet.autofit()
3721
+
3722
+ def write_withdraw_month_report(self):
3723
+ excel_path = create_file_path(self.config.excel_withdraw_month)
3724
+
3725
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
3726
+
3727
+ header = ['店铺名称', '店铺账号', '供应商名称', '交易单号', '提现时间', '提现成功时间', '更新时间', '提现明细单号',
3728
+ '收款帐户', '收款帐户所在地', '净金额', '保证金', '手续费', '汇率', '收款金额', '提现状态']
3729
+ summary_excel_data = [header]
3730
+ # 先读取提现明细列表写入
3731
+ first_day, last_day = TimeUtils.get_last_month_range_time()
3732
+ cache_file = f'{self.config.auto_dir}/shein/cache/withdraw_list_{first_day}_{last_day}.json'
3733
+ dict_withdraw = read_dict_from_file(cache_file)
3734
+ for store_username, list_withdraw in dict_withdraw.items():
3735
+ store_name = dict_store.get(store_username)
3736
+ supplier_name = self.get_supplier_name(store_username)
3737
+ for withdraw in list_withdraw:
3738
+ row_item = []
3739
+ row_item.append(store_name)
3740
+ row_item.append(store_username)
3741
+ row_item.append(supplier_name)
3742
+ row_item.append(withdraw['withdrawNo'])
3743
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw['createTime']))
3744
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw.get('transferSuccessTime')))
3745
+ row_item.append(TimeUtils.convert_timestamp_to_str(withdraw['lastUpdateTime']))
3746
+ row_item.append(withdraw['transferNo'])
3747
+ row_item.append(withdraw['sourceAccountValue'])
3748
+ row_item.append(withdraw['accountAreaCode'])
3749
+ row_item.append(withdraw['netAmount'])
3750
+ row_item.append(withdraw['depositAmount'])
3751
+ row_item.append(withdraw['commissionAmount'])
3752
+ row_item.append(withdraw['exchangeRate'])
3753
+ row_item.append(withdraw['receivingAmount'])
3754
+ row_item.append(withdraw['withdrawStatusDesc'])
3755
+ summary_excel_data.append(row_item)
3756
+
3757
+ log(summary_excel_data)
3758
+ cache_file = f'{self.config.auto_dir}/shein/cache/wallet_balance_{TimeUtils.today_date()}.json'
3759
+ dict = read_dict_from_file(cache_file)
3760
+ dict_store_manager_shein = self.config.shein_store_manager
3761
+ header2 = [
3762
+ ['店铺名称', '店铺账号', '店长', '可提现金额', '提现中金额', '不可提现金额', '上月已提现金额', '已缴保证金', '最近更新时间'],
3763
+ ['汇总', '', '', '', '', '', '', '', ''],
3764
+ ]
3765
+ wallet_excel_data = header2
3766
+ for store_username, dict_wallet in dict.items():
3767
+ store_name = dict_store.get(store_username)
3768
+ row_item = []
3769
+ row_item.append(store_name)
3770
+ row_item.append(store_username)
3771
+ row_item.append(dict_store_manager_shein.get(str(store_username).lower(), '-'))
3772
+
3773
+ # 检查 dict_wallet 是否为 None
3774
+ if dict_wallet is None:
3775
+ # 如果钱包数据为空,填充默认值
3776
+ row_item.extend([0, 0, 0, '', 0, '-'])
3777
+ else:
3778
+ row_item.append(dict_wallet['detailResponseList'][0].get('withdrawableAmount', 0) if dict_wallet.get(
3779
+ 'detailResponseList') else 0)
3780
+ row_item.append(dict_wallet['detailResponseList'][0].get('withdrawingAmount', 0) if dict_wallet.get(
3781
+ 'detailResponseList') else 0)
3782
+ row_item.append(dict_wallet['detailResponseList'][0].get('noWithdrawableAmount', 0) if dict_wallet.get('detailResponseList') else 0)
3783
+ row_item.append('')
3784
+ row_item.append(dict_wallet['depositDetailResponseList'][0].get('depositAmountPaid', 0) if dict_wallet.get('depositDetailResponseList') else 0)
3785
+ t = dict_wallet['detailResponseList'][0].get('lastUpdateTime', '-') if dict_wallet.get(
3786
+ 'detailResponseList') else '-'
3787
+ t_str = TimeUtils.convert_timestamp_to_str(t) if t != '-' else '-'
3788
+ row_item.append(t_str)
3789
+
3790
+ wallet_excel_data.append(row_item)
3791
+
3792
+ # ========== 使用 batch_excel_operations 统一处理 ==========
3793
+ operations = [
3794
+ ['提现明细汇总', 'write', summary_excel_data],
3795
+ ['提现明细汇总', 'format', self.format_withdraw_detail],
3796
+ ['Sheet1', 'write', sort_by_column(wallet_excel_data, 3)],
3797
+ ['Sheet1', 'format', self.format_withdraw_month_report],
3798
+ ]
3799
+
3800
+ batch_excel_operations(excel_path, operations)
3801
+
3802
+ def format_withdraw_month_report(self, sheet):
3803
+ beautify_title(sheet)
3804
+ column_to_right(sheet, ['金额'])
3805
+ format_to_money(sheet, ['金额'])
3806
+ format_to_datetime(sheet, ['时间'])
3807
+ add_sum_for_cell(sheet, ['可提现金额', '提现中金额', '不可提现金额', '上月已提现金额'])
3808
+ add_formula_for_column(sheet, '上月已提现金额', "=SUMIF('提现明细汇总'!B:B, B3, '提现明细汇总'!O:O)", 3)
3809
+ add_borders(sheet)