qrpa 1.0.13__py3-none-any.whl → 1.1.50__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.
qrpa/shein_excel.py CHANGED
@@ -1,13 +1,2588 @@
1
1
  from .fun_excel import *
2
- from .fun_base import log
3
- from .fun_file import read_dict_from_file, read_dict_from_file_ex, write_dict_to_file, write_dict_to_file_ex
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
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
5
11
 
6
12
  class SheinExcel:
7
13
 
8
- def __init__(self, config):
9
- self.config = config
10
- pass
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 write_week_finance_report(self):
437
+ cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_lz_{TimeUtils.today_date()}.json'
438
+ dict = read_dict_from_file(cache_file)
439
+ dict_key = f'{self.config.auto_dir}/shein/dict/dict_store_bak_stat_{TimeUtils.today_date()}.json'
440
+ dict_store_bak_stat = read_dict_from_file(dict_key)
441
+ data = []
442
+ for key, val in dict.items():
443
+ data.append(val)
444
+ log(data)
445
+ for item in data:
446
+ store_username = item[1]
447
+ item[9] = dict_store_bak_stat[store_username][0]
448
+ item[10] = dict_store_bak_stat[store_username][1]
449
+
450
+ data.sort(key=lambda row: row[8], reverse=True)
451
+ excel_path = create_file_path(self.config.excel_week_report)
452
+ sheet_name = '按店铺汇总'
453
+
454
+ date_A = f'新品上架数量\n({TimeUtils.get_past_nth_day(29, TimeUtils.get_month_first_day())},{TimeUtils.get_past_nth_day(29, TimeUtils.get_yesterday())})'
455
+ date_B = f'成功转备货款\n({TimeUtils.get_month_first_day()},{TimeUtils.get_yesterday()})'
456
+
457
+ data.insert(0, ['汇总', '', '', '', '', '', '', '', '', '', '', '', ''])
458
+ data.insert(0, ['店铺名称', '店铺账号', '店长', '在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '汇总',
459
+ '销售出库金额', date_A, date_B, '成功率', '导出时间'])
460
+ write_data(excel_path, sheet_name, data)
461
+ app, wb, sheet = open_excel(excel_path, sheet_name)
462
+ beautify_title(sheet)
463
+
464
+ self.dealFundsExcelFormat(sheet)
465
+ format_to_percent(sheet, ['成功率'], 0)
466
+ add_formula_for_column(sheet, '成功率', '=IF(J2=0, 0, k2/J2)', 2)
467
+ add_formula_for_column(sheet, date_A, "=COUNTIF('新品转备货款明细'!A:A, B3)", 3)
468
+ add_formula_for_column(sheet, date_B, "=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)", 3)
469
+ add_sum_for_cell(sheet, [date_A, date_B])
470
+ column_to_right(sheet, ['金额', '汇总'])
471
+ sheet.autofit()
472
+ autofit_column(sheet, [date_A, date_B])
473
+ delete_sheet_if_exists(wb, 'Sheet1')
474
+ wb.save()
475
+ close_excel(app, wb)
476
+
477
+ new_data = data
478
+ new_data = aggregate_by_column(new_data, '店长')
479
+ new_data_sorted = new_data[2:]
480
+ new_data_sorted.sort(key=lambda row: row[8], reverse=True)
481
+
482
+ sheet_name = '按店长汇总'
483
+ write_data(excel_path, sheet_name, data[:2] + new_data_sorted)
484
+ app, wb, sheet = open_excel(excel_path, sheet_name)
485
+ add_borders(sheet)
486
+ format_to_money(sheet, ['金额', '成本'])
487
+ format_to_datetime(sheet, ['时间'])
488
+ format_to_percent(sheet, ['成功率'], 0)
489
+ add_formula_for_column(sheet, '成功率', '=IF(J2=0, 0, k2/J2)', 2)
490
+ # 聚合的不能使用这种公式
491
+ # add_formula_for_column(sheet, '新品上架数量',"=COUNTIF('新品转备货款明细'!A:A, B3)",3)
492
+ # add_formula_for_column(sheet, '成功转备货款',"=COUNTIFS('新品转备货款明细'!A:A, B3, '新品转备货款明细'!G:G, 1)",3)
493
+ add_sum_for_cell(sheet, ['在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '汇总', '销售出库金额',
494
+ date_A, date_B])
495
+ clear_for_cell(sheet, ['店铺账号', '导出时间'])
496
+ add_formula_for_column(sheet, '汇总', f'=SUM(D3:G3)', 3)
497
+ set_title_style(sheet)
498
+ column_to_right(sheet, ['金额', '汇总'])
499
+ # sheet.autofit()
500
+ autofit_column(sheet, ['店铺名称', '店铺账号', date_A, date_B, '导出时间'])
501
+ wb.save()
502
+ close_excel(app, wb)
503
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
504
+
505
+ def write_return_list_range(self, erp, start_date, end_date):
506
+ header = ['退货单号', '退货出库时间', '签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '商家SKU', '属性集', 'SKU退货数量', '平台SKU', 'ERP默认供货商', 'ERP成本', '包裹名', '包裹号', '退货计划单号', '订单号', '发货单', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址']
507
+ excel_data = [header]
508
+
509
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
510
+
511
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
512
+ dict = read_dict_from_file(cache_file)
513
+ for store_username, shein_back_list in dict.items():
514
+ for item in shein_back_list:
515
+ store_name = dict_store.get(store_username)
516
+
517
+ returnOrderId = item['id']
518
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
519
+ return_detail = read_dict_from_file(cache_file)
520
+ if len(return_detail) == 0:
521
+ continue
522
+
523
+ returnOrderNo = item['returnOrderNo']
524
+ returnOrderTypeName = item['returnOrderTypeName']
525
+ returnOrderStatusName = item['returnOrderStatusName']
526
+ returnReasonTypeName = item['returnReasonTypeName']
527
+ returnReason = item['returnReason']
528
+ waitReturnQuantity = item['waitReturnQuantity']
529
+ skcReturnQuantity = item['returnQuantity']
530
+ returnAmount = item['returnAmount']
531
+ currencyCode = item['currencyCode']
532
+ returnPlanNo = item['returnPlanNo']
533
+ sellerOrderNo = item['sellerOrderNo']
534
+ sellerDeliveryNo = item['sellerDeliveryNo']
535
+ completeTime = item['completeTime']
536
+ returnWayTypeName = item['returnWayTypeName']
537
+ returnExpressCompanyName = item['returnExpressCompanyName']
538
+ expressNoList = item['expressNoList']
539
+ returnAddress = item['returnAddress']
540
+ sellerContract = item['sellerContract']
541
+ sellerContractPhone = item['sellerContractPhone']
542
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
543
+ if item['returnScrapType'] == 1:
544
+ urls = item.get('qc_report_url', '-')
545
+ else:
546
+ urls = '\n'.join(item['rejectPicUrlList'])
547
+
548
+ for box_list in return_detail:
549
+ for package_list in box_list['boxList']:
550
+ package_name = package_list['packageName']
551
+ package_no = package_list['returnBoxNo']
552
+ for skc_item in package_list['goods']:
553
+ skc_img = skc_item['imgPath']
554
+ skc = skc_item['skc']
555
+ supplierCode = skc_item['supplierCode']
556
+ for sku_item in skc_item['details']:
557
+ platformSku = sku_item['platformSku']
558
+ supplierSku = sku_item['supplierSku']
559
+ suffixZh = sku_item['suffixZh']
560
+ returnQuantity = sku_item['returnQuantity']
561
+
562
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
563
+ skc_info = f'SKC: {skc}\n供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
564
+
565
+ row_item = []
566
+ row_item.append(returnOrderNo)
567
+ row_item.append(completeTime)
568
+ row_item.append(isSign)
569
+ row_item.append(store_info)
570
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
571
+ row_item.append(returnReasonTypeName)
572
+ row_item.append(returnReason)
573
+ row_item.append(skc_img)
574
+ row_item.append(skc_info)
575
+ row_item.append(supplierSku)
576
+ row_item.append(suffixZh)
577
+ row_item.append(returnQuantity)
578
+ row_item.append(platformSku)
579
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
580
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
581
+ row_item.append(package_name)
582
+ row_item.append(package_no)
583
+ row_item.append(returnPlanNo)
584
+ row_item.append(sellerOrderNo)
585
+ row_item.append(sellerDeliveryNo)
586
+ row_item.append(returnWayTypeName)
587
+ row_item.append(returnExpressCompanyName)
588
+ row_item.append(expressNoList)
589
+ row_item.append(returnAddress)
590
+ row_item.append(sellerContract)
591
+ row_item.append(sellerContractPhone)
592
+ row_item.append(urls)
593
+
594
+ excel_data.append(row_item)
595
+
596
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
597
+ write_dict_to_file(cache_file_excel, excel_data)
598
+
599
+ sheet_name = '希音退货列表'
600
+ batch_excel_operations(self.config.excel_return_list, [
601
+ (sheet_name, 'write', excel_data, ['W', 'Z']),
602
+ (sheet_name, 'format', self.format_return_list)
603
+ ])
604
+
605
+ # 退货列表
606
+ def write_return_list(self, erp, start_date, end_date):
607
+ header = ['退货单号', '退货出库时间', '签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '商家SKU', '属性集', 'SKU退货数量', '平台SKU', 'ERP默认供货商', 'ERP成本', '包裹名', '包裹号', '退货计划单号', '订单号', '发货单', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址']
608
+ excel_data = [header]
609
+
610
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
611
+
612
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
613
+ dict = read_dict_from_file(cache_file)
614
+ for store_username, shein_back_list in dict.items():
615
+ for item in shein_back_list:
616
+ store_name = dict_store.get(store_username)
617
+
618
+ returnOrderId = item['id']
619
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
620
+ return_detail = read_dict_from_file(cache_file)
621
+ if len(return_detail) == 0:
622
+ continue
623
+
624
+ returnOrderNo = item['returnOrderNo']
625
+ returnOrderTypeName = item['returnOrderTypeName']
626
+ returnOrderStatusName = item['returnOrderStatusName']
627
+ returnReasonTypeName = item['returnReasonTypeName']
628
+ returnReason = item['returnReason']
629
+ waitReturnQuantity = item['waitReturnQuantity']
630
+ skcReturnQuantity = item['returnQuantity']
631
+ returnAmount = item['returnAmount']
632
+ currencyCode = item['currencyCode']
633
+ returnPlanNo = item['returnPlanNo']
634
+ sellerOrderNo = item['sellerOrderNo']
635
+ sellerDeliveryNo = item['sellerDeliveryNo']
636
+ completeTime = item['completeTime']
637
+ returnWayTypeName = item['returnWayTypeName']
638
+ returnExpressCompanyName = item['returnExpressCompanyName']
639
+ expressNoList = item['expressNoList']
640
+ returnAddress = item['returnAddress']
641
+ sellerContract = item['sellerContract']
642
+ sellerContractPhone = item['sellerContractPhone']
643
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
644
+ if item['returnScrapType'] == 1:
645
+ urls = item.get('qc_report_url', '-')
646
+ else:
647
+ urls = '\n'.join(item['rejectPicUrlList'])
648
+
649
+ for box_list in return_detail:
650
+ for package_list in box_list['boxList']:
651
+ package_name = package_list['packageName']
652
+ package_no = package_list['returnBoxNo']
653
+ for skc_item in package_list['goods']:
654
+ skc_img = skc_item['imgPath']
655
+ skc = skc_item['skc']
656
+ supplierCode = skc_item['supplierCode']
657
+ for sku_item in skc_item['details']:
658
+ platformSku = sku_item['platformSku']
659
+ supplierSku = sku_item['supplierSku']
660
+ suffixZh = sku_item['suffixZh']
661
+ returnQuantity = sku_item['returnQuantity']
662
+
663
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
664
+ skc_info = f'SKC: {skc}\n供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
665
+
666
+ row_item = []
667
+ row_item.append(returnOrderNo)
668
+ row_item.append(completeTime)
669
+ row_item.append(isSign)
670
+ row_item.append(store_info)
671
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
672
+ row_item.append(returnReasonTypeName)
673
+ row_item.append(returnReason)
674
+ row_item.append(skc_img)
675
+ row_item.append(skc_info)
676
+ row_item.append(supplierSku)
677
+ row_item.append(suffixZh)
678
+ row_item.append(returnQuantity)
679
+ row_item.append(platformSku)
680
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
681
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
682
+ row_item.append(package_name)
683
+ row_item.append(package_no)
684
+ row_item.append(returnPlanNo)
685
+ row_item.append(sellerOrderNo)
686
+ row_item.append(sellerDeliveryNo)
687
+ row_item.append(returnWayTypeName)
688
+ row_item.append(returnExpressCompanyName)
689
+ row_item.append(expressNoList)
690
+ row_item.append(returnAddress)
691
+ row_item.append(sellerContract)
692
+ row_item.append(sellerContractPhone)
693
+ row_item.append(urls)
694
+
695
+ excel_data.append(row_item)
696
+
697
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
698
+ write_dict_to_file(cache_file_excel, excel_data)
699
+
700
+ # sheet_name = '希音退货列表'
701
+ # batch_excel_operations(self.config.excel_return_list, [
702
+ # (sheet_name, 'write', excel_data, ['W', 'Z']),
703
+ # (sheet_name, 'format', self.format_return_list)
704
+ # ])
705
+
706
+ excel_data = [header]
707
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
708
+ dict = read_dict_from_file(cache_file)
709
+ for store_username, shein_back_list in dict.items():
710
+ for item in shein_back_list:
711
+ store_name = dict_store.get(store_username)
712
+
713
+ returnOrderId = item['id']
714
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
715
+ return_detail = read_dict_from_file(cache_file)
716
+ if len(return_detail) == 0:
717
+ continue
718
+
719
+ returnOrderNo = item['returnOrderNo']
720
+ returnOrderTypeName = item['returnOrderTypeName']
721
+ returnOrderStatusName = item['returnOrderStatusName']
722
+ returnReasonTypeName = item['returnReasonTypeName']
723
+ returnReason = item['returnReason']
724
+ waitReturnQuantity = item['waitReturnQuantity']
725
+ skcReturnQuantity = item['returnQuantity']
726
+ returnAmount = item['returnAmount']
727
+ currencyCode = item['currencyCode']
728
+ returnPlanNo = item['returnPlanNo']
729
+ sellerOrderNo = item['sellerOrderNo']
730
+ sellerDeliveryNo = item['sellerDeliveryNo']
731
+ completeTime = item['completeTime']
732
+ returnWayTypeName = item['returnWayTypeName']
733
+ returnExpressCompanyName = item['returnExpressCompanyName']
734
+ expressNoList = item['expressNoList']
735
+ returnAddress = item['returnAddress']
736
+ sellerContract = item['sellerContract']
737
+ sellerContractPhone = item['sellerContractPhone']
738
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
739
+ if item['returnScrapType'] == 1:
740
+ urls = item.get('qc_report_url', '-')
741
+ else:
742
+ urls = '\n'.join(item['rejectPicUrlList'])
743
+
744
+ for box_list in return_detail:
745
+ for package_list in box_list['boxList']:
746
+ package_name = package_list['packageName']
747
+ package_no = package_list['returnBoxNo']
748
+ for skc_item in package_list['goods']:
749
+ skc_img = skc_item['imgPath']
750
+ skc = skc_item['skc']
751
+ supplierCode = skc_item['supplierCode']
752
+ for sku_item in skc_item['details']:
753
+ platformSku = sku_item['platformSku']
754
+ supplierSku = sku_item['supplierSku']
755
+ suffixZh = sku_item['suffixZh']
756
+ returnQuantity = sku_item['returnQuantity']
757
+
758
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
759
+ skc_info = f'SKC: {skc}\n供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
760
+
761
+ row_item = []
762
+ row_item.append(returnOrderNo)
763
+ row_item.append(completeTime)
764
+ row_item.append(isSign)
765
+ row_item.append(store_info)
766
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
767
+ row_item.append(returnReasonTypeName)
768
+ row_item.append(returnReason)
769
+ row_item.append(skc_img)
770
+ row_item.append(skc_info)
771
+ row_item.append(supplierSku)
772
+ row_item.append(suffixZh)
773
+ row_item.append(returnQuantity)
774
+ row_item.append(platformSku)
775
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
776
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
777
+ row_item.append(package_name)
778
+ row_item.append(package_no)
779
+ row_item.append(returnPlanNo)
780
+ row_item.append(sellerOrderNo)
781
+ row_item.append(sellerDeliveryNo)
782
+ row_item.append(returnWayTypeName)
783
+ row_item.append(returnExpressCompanyName)
784
+ row_item.append(expressNoList)
785
+ row_item.append(returnAddress)
786
+ row_item.append(sellerContract)
787
+ row_item.append(sellerContractPhone)
788
+ row_item.append(urls)
789
+
790
+ excel_data.append(row_item)
791
+
792
+ sheet_name = '昨日退货列表'
793
+
794
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{TimeUtils.today_date()}.json'
795
+ write_dict_to_file(cache_file_excel, excel_data)
796
+
797
+ batch_excel_operations(self.config.excel_return_list, [
798
+ (sheet_name, 'write', excel_data, ['W', 'Z']),
799
+ (sheet_name, 'format', self.format_return_list),
800
+ ('Sheet1', 'delete')
801
+ ])
802
+
803
+ def format_return_list(self, sheet):
804
+ merge_by_column_v2(sheet, '包裹号', ['包裹名'])
805
+ merge_by_column_v2(sheet, '退货单号', ['签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '退货计划单号', '订单号', '发货单', '退货出库时间', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址'])
806
+ beautify_title(sheet)
807
+ add_borders(sheet)
808
+ format_to_datetime(sheet, ['时间'])
809
+ format_to_money(sheet, ['单价', '金额', '成本'])
810
+ column_to_right(sheet, ['单价', '金额', '成本'])
811
+ wrap_column(sheet, ['退货原因', '退货地址', '入库问题图片地址'])
812
+ autofit_column(sheet, ['店铺信息', '店铺别名', 'SKC信息'])
813
+ column_to_left(sheet, ['店铺信息', '商家SKU', '供方货号', '属性集', 'SKC信息', '退货地址'])
814
+ specify_column_width(sheet, ['退货原因', 'SKC信息', '商家SKU', '退货地址'], 200 / 6)
815
+ InsertImageV2(sheet, ['SKC图片'])
816
+
817
+ def dealReturn(self, sheet):
818
+ # 遍历可用行
819
+ used_range_row = sheet.range('A1').expand('down')
820
+ last_row = len(used_range_row)
821
+
822
+ col_0 = find_column_by_data(sheet, 1, '实际退货/报废总数')
823
+ if last_row < 3:
824
+ fm = f'=SUM({col_0}3:{col_0}3)'
825
+ else:
826
+ fm = f'=SUM({col_0}3:{col_0}{last_row})'
827
+
828
+ sheet.range(f'{col_0}2').formula = fm
829
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
830
+
831
+ for i, cell in enumerate(used_range_row):
832
+ row = i + 1
833
+ if row < 3:
834
+ continue
835
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
836
+ sheet.range(f'{row}:{row}').font.size = 11
837
+
838
+ used_range_col = sheet.range('A1').expand('right')
839
+ for j, cell in enumerate(used_range_col):
840
+ col = j + 1
841
+ col_name = index_to_column_name(col)
842
+ col_val = sheet.range(f'{col_name}1').value
843
+ if col_val not in ['']:
844
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
845
+
846
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
847
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
848
+
849
+ if '时间' in col_val:
850
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
851
+
852
+ if '月份' == col_val:
853
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
854
+
855
+ # # 设置标题栏字体颜色与背景色
856
+ # sheet.range(f'{col_name}1').color = (252,228,214)
857
+ # sheet.range(f'{col_name}1').font.size = 12
858
+ # sheet.range(f'{col_name}1').font.bold = True
859
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
860
+
861
+ # 所有列水平居中和垂直居中
862
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
863
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
864
+
865
+ # 水平对齐:
866
+ # -4108:居中
867
+ # -4131:左对齐
868
+ # -4152:右对齐
869
+
870
+ # 垂直对齐:
871
+ # -4108:居中
872
+ # -4160:顶部对齐
873
+ # -4107:底部对齐
874
+
875
+ add_borders(sheet)
876
+
877
+ # 获取第一行和第二行
878
+ rows = sheet.range('1:2')
879
+ # 设置字体名称
880
+ rows.font.name = '微软雅黑'
881
+ # 设置字体大小
882
+ rows.font.size = 11
883
+ # 设置字体加粗
884
+ rows.font.bold = True
885
+ # 设置标题栏字体颜色与背景色
886
+ rows.color = (252, 228, 214)
887
+ # 设置行高
888
+ rows.row_height = 30
889
+
890
+ def dealReplenish(self, sheet):
891
+ # 遍历可用行
892
+ used_range_row = sheet.range('A1').expand('down')
893
+ last_row = len(used_range_row)
894
+ # 获取最后一行的索引
895
+ last_col = index_to_column_name(sheet.range('A1').end('right').column)
896
+ # last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
897
+
898
+ col_3 = find_column_by_data(sheet, 1, '总金额')
899
+ if last_row < 3:
900
+ fm = f'=SUM({col_3}3:{col_3}3)'
901
+ else:
902
+ fm = f'=SUM({col_3}3:{col_3}{last_row})'
903
+
904
+ sheet.range(f'{col_3}2').formula = fm
905
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
906
+
907
+ for i, cell in enumerate(used_range_row):
908
+ row = i + 1
909
+ if row < 3:
910
+ continue
911
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
912
+ sheet.range(f'{row}:{row}').font.size = 11
913
+
914
+ used_range_col = sheet.range('A1').expand('right')
915
+ for j, cell in enumerate(used_range_col):
916
+ col = j + 1
917
+ col_name = index_to_column_name(col)
918
+ col_val = sheet.range(f'{col_name}1').value
919
+ if col_val not in ['']:
920
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
921
+
922
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
923
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
924
+
925
+ if '时间' in col_val:
926
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
927
+
928
+ if '月份' == col_val:
929
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
930
+
931
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
932
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
933
+ # 所有列水平居中和垂直居中
934
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
935
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
936
+
937
+ add_borders(sheet)
938
+
939
+ # === 批量字体设置 ===
940
+ if last_row > 3:
941
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
942
+ data_range.api.Font.Name = "Calibri"
943
+ data_range.api.Font.Size = 11
944
+
945
+ # 获取第一行和第二行
946
+ rows = sheet.range('1:2')
947
+ # 设置字体名称
948
+ rows.font.name = '微软雅黑'
949
+ # 设置字体大小
950
+ rows.font.size = 11
951
+ # 设置字体加粗
952
+ rows.font.bold = True
953
+ # 设置标题栏字体颜色与背景色
954
+ rows.color = (252, 228, 214)
955
+ # 设置行高
956
+ rows.row_height = 30
957
+
958
+ def dealSheinStock(self, sheet):
959
+ col_0 = find_column_by_data(sheet, 1, '期末库存数量')
960
+ col_1 = find_column_by_data(sheet, 1, '期末库存金额')
961
+ col_2 = find_column_by_data(sheet, 1, '单价成本')
962
+ col_3 = find_column_by_data(sheet, 1, '希音仓成本总额')
963
+
964
+ col_4 = find_column_by_data(sheet, 1, '期初库存数量')
965
+ col_5 = find_column_by_data(sheet, 1, '期初库存金额')
966
+
967
+ col_6 = find_column_by_data(sheet, 1, '入库数量')
968
+ col_7 = find_column_by_data(sheet, 1, '入库金额')
969
+ col_8 = find_column_by_data(sheet, 1, '出库数量')
970
+ col_9 = find_column_by_data(sheet, 1, '出库金额')
971
+
972
+ col_10 = find_column_by_data(sheet, 1, '出库成本总额')
973
+ col_11 = find_column_by_data(sheet, 1, '出库利润')
974
+ col_12 = find_column_by_data(sheet, 1, '出库利润率')
975
+
976
+ # 遍历可用行
977
+ used_range_row = sheet.range('A1').expand('down')
978
+ last_row = len(used_range_row)
979
+ # # 获取最后一行的索引
980
+ last_col = index_to_column_name(sheet.range('A1').end('right').column)
981
+ # last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
982
+ if last_row > 2:
983
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
984
+ sheet.range(f'{col_0}2').font.color = (225, 0, 0)
985
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
986
+ sheet.range(f'{col_1}2').font.color = (225, 0, 0)
987
+ sheet.range(f'{col_3}2').formula = f'=SUM({col_3}3:{col_3}{last_row})'
988
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
989
+
990
+ sheet.range(f'{col_4}2').formula = f'=SUM({col_4}3:{col_4}{last_row})'
991
+ sheet.range(f'{col_4}2').font.color = (225, 0, 0)
992
+ sheet.range(f'{col_5}2').formula = f'=SUM({col_5}3:{col_5}{last_row})'
993
+ sheet.range(f'{col_5}2').font.color = (225, 0, 0)
994
+
995
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
996
+ sheet.range(f'{col_6}2').font.color = (225, 0, 0)
997
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
998
+ sheet.range(f'{col_7}2').font.color = (225, 0, 0)
999
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1000
+ sheet.range(f'{col_8}2').font.color = (225, 0, 0)
1001
+ sheet.range(f'{col_9}2').formula = f'=SUM({col_9}3:{col_9}{last_row})'
1002
+ sheet.range(f'{col_9}2').font.color = (225, 0, 0)
1003
+
1004
+ sheet.range(f'{col_10}2').formula = f'=SUM({col_10}3:{col_10}{last_row})'
1005
+ sheet.range(f'{col_10}2').font.color = (225, 0, 0)
1006
+
1007
+ sheet.range(f'{col_11}2').formula = f'=SUM({col_11}3:{col_11}{last_row})'
1008
+ sheet.range(f'{col_11}2').font.color = (225, 0, 0)
1009
+
1010
+ if last_row > 3:
1011
+ # 设置毛利润和毛利润率列公式与格式
1012
+ sheet.range(f'{col_3}3').formula = f'={col_0}3*{col_2}3'
1013
+ # AutoFill 快速填充到所有行(3 到 last_row)
1014
+ sheet.range(f'{col_3}3').api.AutoFill(sheet.range(f'{col_3}3:{col_3}{last_row}').api)
1015
+
1016
+ sheet.range(f'{col_10}3').formula = f'={col_8}3*{col_2}3'
1017
+ sheet.range(f'{col_10}3').api.AutoFill(sheet.range(f'{col_10}3:{col_10}{last_row}').api)
1018
+
1019
+ sheet.range(f'{col_11}3').formula = f'={col_9}3-{col_10}3'
1020
+ sheet.range(f'{col_11}3').api.AutoFill(sheet.range(f'{col_11}3:{col_11}{last_row}').api)
1021
+
1022
+ sheet.range(f'{col_12}3').number_format = '0.00%'
1023
+ sheet.range(f'{col_12}3').formula = f'=IF({col_9}3 > 0,{col_11}3/{col_9}3,0)'
1024
+ sheet.range(f'{col_12}3').api.AutoFill(sheet.range(f'{col_12}3:{col_12}{last_row}').api)
1025
+
1026
+ used_range_col = sheet.range('A1').expand('right')
1027
+ for j, cell in enumerate(used_range_col):
1028
+ col = j + 1
1029
+ col_name = index_to_column_name(col)
1030
+ col_val = sheet.range(f'{col_name}1').value
1031
+ if col_val not in ['']:
1032
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1033
+
1034
+ if col_val in ['业务单号']:
1035
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1036
+
1037
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or ('利润' in col_val and '率' not in col_val):
1038
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1039
+
1040
+ if '时间' in col_val:
1041
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1042
+
1043
+ if '月份' == col_val:
1044
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm'
1045
+
1046
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1047
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1048
+ # 所有列水平居中和垂直居中
1049
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1050
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1051
+
1052
+ add_borders(sheet)
1053
+
1054
+ # === 批量字体设置 ===
1055
+ if last_row > 3:
1056
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1057
+ data_range.api.Font.Name = "Calibri"
1058
+ data_range.api.Font.Size = 11
1059
+
1060
+ set_title_style(sheet)
1061
+
1062
+ def dealSalesPercentageExcel(self, sheet):
1063
+ col_0 = find_column_by_data(sheet, 1, '商家SKU')
1064
+ col_1 = find_column_by_data(sheet, 1, '售出数量')
1065
+ col_2 = find_column_by_data(sheet, 1, '销量占比')
1066
+ col_3 = find_column_by_data(sheet, 1, '售出金额')
1067
+ col_4 = find_column_by_data(sheet, 1, '销售额占比')
1068
+ col_5 = find_column_by_data(sheet, 1, '利润')
1069
+ col_6 = find_column_by_data(sheet, 1, '利润占比')
1070
+ col_7 = find_column_by_data(sheet, 1, 'SKU图片')
1071
+
1072
+ # 遍历可用行
1073
+ used_range_row = sheet.range('B1').expand('down')
1074
+ last_row = len(used_range_row)
1075
+ if last_row > 2:
1076
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1077
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1078
+ sheet.range(f'{col_3}2').formula = f'=SUM({col_3}3:{col_3}{last_row})'
1079
+ sheet.range(f'{col_3}2').font.color = (255, 0, 0)
1080
+ sheet.range(f'{col_5}2').formula = f'=SUM({col_5}3:{col_5}{last_row})'
1081
+ sheet.range(f'{col_5}2').font.color = (255, 0, 0)
1082
+ # sheet.range(f'{col_7}1:{col_7}2').merge()
1083
+
1084
+ for i, cell in enumerate(used_range_row):
1085
+ row = i + 1
1086
+ if row < 3:
1087
+ continue
1088
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1089
+ sheet.range(f'{row}:{row}').font.size = 11
1090
+
1091
+ sheet.range(f'{col_2}{row}').formula = f'={col_1}{row}/{col_1}2'
1092
+ sheet.range(f'{col_4}{row}').formula = f'={col_3}{row}/{col_3}2'
1093
+ sheet.range(f'{col_6}{row}').formula = f'={col_5}{row}/{col_5}2'
1094
+
1095
+ used_range_col = sheet.range('A1').expand('right')
1096
+ for j, cell in enumerate(used_range_col):
1097
+ col = j + 1
1098
+ col_name = index_to_column_name(col)
1099
+ col_val = sheet.range(f'{col_name}1').value
1100
+ if col_val not in ['']:
1101
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1102
+
1103
+ if col_val in ['占比']:
1104
+ sheet.range(f'{col_name}:{col_name}').number_format = '0.00%'
1105
+
1106
+ if ('价' in col_val or '成本' in col_val or '金额' in col_val or '利润' == 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
+ # # 设置标题栏字体颜色与背景色
1113
+ # sheet.range(f'{col_name}1').color = (252,228,214)
1114
+ # sheet.range(f'{col_name}1').font.size = 12
1115
+ # sheet.range(f'{col_name}1').font.bold = True
1116
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
1117
+
1118
+ # 所有列水平居中和垂直居中
1119
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1120
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1121
+
1122
+ # 水平对齐:
1123
+ # -4108:居中
1124
+ # -4131:左对齐
1125
+ # -4152:右对齐
1126
+
1127
+ # 垂直对齐:
1128
+ # -4108:居中
1129
+ # -4160:顶部对齐
1130
+ # -4107:底部对齐
1131
+
1132
+ add_borders(sheet)
1133
+
1134
+ # 获取第一行和第二行
1135
+ rows = sheet.range('1:2')
1136
+ # 设置字体名称
1137
+ rows.font.name = '微软雅黑'
1138
+ # 设置字体大小
1139
+ rows.font.size = 11
1140
+ # 设置字体加粗
1141
+ rows.font.bold = True
1142
+ # 设置标题栏字体颜色与背景色
1143
+ rows.color = (252, 228, 214)
1144
+ # 设置行高
1145
+ rows.row_height = 30
1146
+
1147
+ def dealMonthNoSettleMentExcel(self, sheet):
1148
+ col_0 = find_column_by_data(sheet, 1, '数量')
1149
+ col_2 = find_column_by_data(sheet, 1, '金额')
1150
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1151
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1152
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1153
+ col_8 = find_column_by_data(sheet, 1, '成本总额')
1154
+
1155
+ # 设置格式
1156
+ used_range_col = sheet.range('A1').expand('right')
1157
+ for j, cell in enumerate(used_range_col):
1158
+ col = j + 1
1159
+ col_name = index_to_column_name(col)
1160
+ col_val = sheet.range(f'{col_name}1').value
1161
+ if col_val not in ['']:
1162
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1163
+
1164
+ if col_val in ['业务单号']:
1165
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1166
+
1167
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1168
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1169
+
1170
+ if '时间' in col_val:
1171
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1172
+
1173
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1174
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1175
+ # 所有列水平居中和垂直居中
1176
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1177
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1178
+
1179
+ # 批量设置公式
1180
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1181
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1182
+ if last_row > 2:
1183
+ # 第3行公式(填一次)
1184
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1185
+ sheet.range(f'{col_8}2').font.color = (255, 0, 0)
1186
+ # AutoFill 快速填充到所有行(3 到 last_row)
1187
+ sheet.range(f'{col_8}3').formula = f'={col_3}3*{col_0}3'
1188
+
1189
+ if last_row > 3:
1190
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1191
+
1192
+ sheet.range(f'{col_4}1').column_width = 0
1193
+
1194
+ # 批量设置边框
1195
+ add_borders(sheet)
1196
+
1197
+ if last_row > 2:
1198
+ # === 批量字体设置 ===
1199
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1200
+ data_range.api.Font.Name = "Calibri"
1201
+ data_range.api.Font.Size = 11
1202
+
1203
+ set_title_style(sheet)
1204
+
1205
+ def dealMonthBackDetailExcel(self, sheet, summary=0):
1206
+ col_0 = find_column_by_data(sheet, 1, '数量')
1207
+ col_2 = find_column_by_data(sheet, 1, '金额')
1208
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1209
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1210
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1211
+ col_8 = find_column_by_data(sheet, 1, '成本总额')
1212
+
1213
+ # 设置格式
1214
+ used_range_col = sheet.range('A1').expand('right')
1215
+ for j, cell in enumerate(used_range_col):
1216
+ col = j + 1
1217
+ col_name = index_to_column_name(col)
1218
+ col_val = sheet.range(f'{col_name}1').value
1219
+ if col_val not in ['']:
1220
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1221
+
1222
+ if col_val in ['业务单号']:
1223
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1224
+
1225
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1226
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1227
+
1228
+ if '时间' in col_val:
1229
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1230
+
1231
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1232
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1233
+ # 所有列水平居中和垂直居中
1234
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1235
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1236
+
1237
+ # 批量设置公式
1238
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1239
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1240
+ if summary == 1:
1241
+ if last_row > 1:
1242
+ sheet.range(f'{col_8}2').formula = f'={col_3}2*{col_0}2'
1243
+ if last_row > 2:
1244
+ # AutoFill 快速填充到所有行(3 到 last_row)
1245
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1246
+ else:
1247
+ if last_row > 2:
1248
+ # 合计行设置
1249
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1250
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1251
+
1252
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1253
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1254
+
1255
+ sheet.range(f'{col_8}2').formula = f'=SUM({col_8}3:{col_8}{last_row})'
1256
+ sheet.range(f'{col_8}2').font.color = (255, 0, 0)
1257
+
1258
+ # AutoFill 快速填充到所有行(3 到 last_row)
1259
+ sheet.range(f'{col_8}3').formula = f'={col_3}3*{col_0}3'
1260
+
1261
+ if last_row > 3:
1262
+ sheet.range(f'{col_8}3').api.AutoFill(sheet.range(f'{col_8}3:{col_8}{last_row}').api)
1263
+
1264
+ set_title_style(sheet)
1265
+
1266
+ sheet.range(f'{col_4}1').column_width = 0
1267
+
1268
+ # 批量设置边框
1269
+ add_borders(sheet)
1270
+
1271
+ if last_row > 3:
1272
+ # === 批量字体设置 ===
1273
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1274
+ data_range.api.Font.Name = "Calibri"
1275
+ data_range.api.Font.Size = 11
1276
+
1277
+ def dealMonthSalesDetailExcel(self, sheet):
1278
+ col_0 = find_column_by_data(sheet, 1, '数量')
1279
+ col_1 = find_column_by_data(sheet, 1, '利润')
1280
+ col_2 = find_column_by_data(sheet, 1, '金额')
1281
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1282
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1283
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1284
+ col_6 = find_column_by_data(sheet, 1, '售出数量')
1285
+ col_7 = find_column_by_data(sheet, 1, '售出金额')
1286
+
1287
+ # 设置格式
1288
+ used_range_col = sheet.range('A1').expand('right')
1289
+ for j, cell in enumerate(used_range_col):
1290
+ col = j + 1
1291
+ col_name = index_to_column_name(col)
1292
+ col_val = sheet.range(f'{col_name}1').value
1293
+ if col_val not in ['']:
1294
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1295
+
1296
+ if col_val in ['业务单号']:
1297
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1298
+
1299
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1300
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1301
+
1302
+ if '时间' in col_val:
1303
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1304
+
1305
+ # 水平对齐: # -4108:居中 # -4131:左对齐 # -4152:右对齐
1306
+ # 垂直对齐: # -4108:居中 # -4160:顶部对齐 # -4107:底部对齐
1307
+ # 所有列水平居中和垂直居中
1308
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1309
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1310
+
1311
+ # 批量设置公式
1312
+ last_col = index_to_column_name(sheet.range('A1').end('right').column) # 获取最后一行的索引
1313
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
1314
+ if last_row > 2:
1315
+ # 第3行公式(填一次)
1316
+ sheet.range(
1317
+ 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)'
1318
+ sheet.range(f'{col_6}3').formula = f'=IF(AND(ISNUMBER({col_3}3),{col_5}3="收入结算"),{col_0}3,0)'
1319
+ sheet.range(f'{col_7}3').formula = f'=IF(AND(ISNUMBER({col_3}3),{col_5}3="收入结算"),{col_2}3,0)'
1320
+
1321
+ if last_row > 3:
1322
+ # AutoFill 快速填充到所有行(3 到 last_row)
1323
+ sheet.range(f'{col_1}3').api.AutoFill(sheet.range(f'{col_1}3:{col_1}{last_row}').api)
1324
+ sheet.range(f'{col_6}3').api.AutoFill(sheet.range(f'{col_6}3:{col_6}{last_row}').api)
1325
+ sheet.range(f'{col_7}3').api.AutoFill(sheet.range(f'{col_7}3:{col_7}{last_row}').api)
1326
+
1327
+ # 合计行设置
1328
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1329
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1330
+
1331
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1332
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1333
+
1334
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1335
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1336
+
1337
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
1338
+ sheet.range(f'{col_6}2').font.color = (255, 0, 0)
1339
+
1340
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
1341
+ sheet.range(f'{col_7}2').font.color = (255, 0, 0)
1342
+
1343
+ sheet.range(f'{col_4}1').column_width = 0
1344
+
1345
+ # 批量设置边框
1346
+ add_borders(sheet)
1347
+
1348
+ if last_row > 3:
1349
+ # === 批量字体设置 ===
1350
+ data_range = sheet.range(f'A3:{last_col}{last_row}')
1351
+ data_range.api.Font.Name = "Calibri"
1352
+ data_range.api.Font.Size = 11
1353
+ log(f'设置字体: A3:{col_7}{last_row}')
1354
+
1355
+ # 获取第一行和第二行
1356
+ rows = sheet.range('1:2')
1357
+ # 设置字体名称
1358
+ rows.font.name = '微软雅黑'
1359
+ # 设置字体大小
1360
+ rows.font.size = 11
1361
+ # 设置字体加粗
1362
+ rows.font.bold = True
1363
+ # 设置标题栏字体颜色与背景色
1364
+ rows.color = (252, 228, 214)
1365
+ # 设置行高
1366
+ rows.row_height = 30
1367
+
1368
+ def calc_month_sales_percentage(self, month_data):
1369
+ df = pd.DataFrame(data=month_data[2:], columns=month_data[:1][0])
1370
+
1371
+ # 确保 "商家SKU" 是字符串
1372
+ df["商家SKU"] = df["商家SKU"].astype(str).str.strip()
1373
+
1374
+ # 确保 "数量", "金额", "单价成本" 是数值类型
1375
+ df["售出数量"] = pd.to_numeric(df["售出数量"], errors="coerce")
1376
+ df["售出金额"] = pd.to_numeric(df["售出金额"], errors="coerce")
1377
+ df["单价成本"] = pd.to_numeric(df["单价成本"], errors="coerce")
1378
+
1379
+ # 重新计算利润
1380
+ df["利润"] = np.where(
1381
+ df["结算类型"] == "收入结算",
1382
+ df["售出金额"] - (df["单价成本"] * df["售出数量"]),
1383
+ 0
1384
+ )
1385
+
1386
+ # 进行分组统计(求和)
1387
+ summary = df.groupby("商家SKU", as_index=False).agg({
1388
+ "售出数量": "sum",
1389
+ "售出金额": "sum",
1390
+ "利润" : "sum",
1391
+ "SKU图片" : "first"
1392
+ })
1393
+
1394
+ # 计算总值
1395
+ total_quantity = summary["售出数量"].sum()
1396
+ total_amount = summary["售出金额"].sum()
1397
+ total_profit = summary["利润"].sum()
1398
+
1399
+ # 计算占比
1400
+ summary["销量占比"] = summary["售出数量"] / total_quantity * 100
1401
+ summary["销售额占比"] = summary["售出金额"] / total_amount * 100
1402
+ summary["利润占比"] = summary["利润"] / total_profit * 100
1403
+
1404
+ # 确保显示 2 位小数,并加上百分号
1405
+ summary["销量占比"] = summary["销量占比"].map(lambda x: f"{x:.2f}%")
1406
+ summary["销售额占比"] = summary["销售额占比"].map(lambda x: f"{x:.2f}%")
1407
+ summary["利润占比"] = summary["利润占比"].map(lambda x: f"{x:.2f}%")
1408
+
1409
+ # 重新排序列
1410
+ summary = summary[["SKU图片", "商家SKU", "售出数量", "销量占比", "售出金额", "销售额占比", "利润", "利润占比"]]
1411
+ summary_list = summary.values.tolist()
1412
+
1413
+ summary_list.insert(0, ['', '合计', '', '', '', '', '', '']) # 把表头插入到数据列表的第一行
1414
+ # 添加标题行(表头)
1415
+ header = summary.columns.tolist()
1416
+ summary_list.insert(0, header) # 把表头插入到数据列表的第一行
1417
+
1418
+ return summary_list
1419
+
1420
+ def dealMonthSalesDetailExcel_old(self, sheet):
1421
+ col_0 = find_column_by_data(sheet, 1, '数量')
1422
+ col_1 = find_column_by_data(sheet, 1, '利润')
1423
+ col_2 = find_column_by_data(sheet, 1, '金额')
1424
+ col_3 = find_column_by_data(sheet, 1, '单价成本')
1425
+ col_4 = find_column_by_data(sheet, 1, 'SKU图片')
1426
+ col_5 = find_column_by_data(sheet, 1, '结算类型')
1427
+ col_6 = find_column_by_data(sheet, 1, '售出数量')
1428
+ col_7 = find_column_by_data(sheet, 1, '售出金额')
1429
+ # 遍历可用行
1430
+ used_range_row = sheet.range('A1').expand('down')
1431
+ last_row = len(used_range_row)
1432
+ for i, cell in enumerate(used_range_row):
1433
+ row = i + 1
1434
+ if row < 3:
1435
+ continue
1436
+ sheet.range(f'{row}:{row}').font.name = 'Calibri'
1437
+ sheet.range(f'{row}:{row}').font.size = 11
1438
+ range0 = f'{col_0}{row}'
1439
+ range2 = f'{col_2}{row}'
1440
+ range3 = f'{col_3}{row}'
1441
+ range5 = f'{col_5}{row}'
1442
+ # 设置毛利润和毛利润率列公式与格式
1443
+ sheet.range(
1444
+ f'{col_1}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range2}-{range3}*{range0},0)'
1445
+ sheet.range(f'{col_6}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range0},0)'
1446
+ sheet.range(f'{col_7}{row}').formula = f'=IF(AND(ISNUMBER({range3}),{range5}="收入结算"),{range2},0)'
1447
+ log(f'处理公式: {row}/{last_row}')
1448
+
1449
+ if last_row > 2:
1450
+ sheet.range(f'{col_0}2').formula = f'=SUM({col_0}3:{col_0}{last_row})'
1451
+ sheet.range(f'{col_0}2').font.color = (255, 0, 0)
1452
+ sheet.range(f'{col_2}2').formula = f'=SUM({col_2}3:{col_2}{last_row})'
1453
+ sheet.range(f'{col_2}2').font.color = (255, 0, 0)
1454
+ sheet.range(f'{col_1}2').formula = f'=SUM({col_1}3:{col_1}{last_row})'
1455
+ sheet.range(f'{col_1}2').font.color = (255, 0, 0)
1456
+ sheet.range(f'{col_6}2').formula = f'=SUM({col_6}3:{col_6}{last_row})'
1457
+ sheet.range(f'{col_6}2').font.color = (255, 0, 0)
1458
+ sheet.range(f'{col_7}2').formula = f'=SUM({col_7}3:{col_7}{last_row})'
1459
+ sheet.range(f'{col_7}2').font.color = (255, 0, 0)
1460
+ sheet.range(f'{col_4}1').column_width = 0
1461
+ # # 设置计算模式为自动计算
1462
+ # sheet.api.Application.Calculation = -4105 # -4105 代表自动计算模式
1463
+ # # 手动触发一次计算
1464
+ # sheet.api.Calculate()
1465
+
1466
+ used_range_col = sheet.range('A1').expand('right')
1467
+ for j, cell in enumerate(used_range_col):
1468
+ col = j + 1
1469
+ col_name = index_to_column_name(col)
1470
+ col_val = sheet.range(f'{col_name}1').value
1471
+ if col_val not in ['']:
1472
+ sheet.range(f'{col_name}:{col_name}').autofit() # 列宽自适应
1473
+
1474
+ if col_val in ['业务单号']:
1475
+ sheet.range(f'{col_name}:{col_name}').number_format = '@'
1476
+
1477
+ if '价' in col_val or '成本' in col_val or '金额' in col_val or '利润' in col_val:
1478
+ sheet.range(f'{col_name}:{col_name}').number_format = '¥#,##0.00'
1479
+
1480
+ if '时间' in col_val:
1481
+ sheet.range(f'{col_name}:{col_name}').number_format = 'yyyy-mm-dd hh:mm:ss'
1482
+
1483
+ # # 设置标题栏字体颜色与背景色
1484
+ # sheet.range(f'{col_name}1').color = (252,228,214)
1485
+ # sheet.range(f'{col_name}1').font.size = 12
1486
+ # sheet.range(f'{col_name}1').font.bold = True
1487
+ # sheet.range(f'{col_name}1').font.color = (0,0, 0)
1488
+
1489
+ # 所有列水平居中和垂直居中
1490
+ sheet.range(f'{col_name}:{col_name}').api.HorizontalAlignment = -4108
1491
+ sheet.range(f'{col_name}:{col_name}').api.VerticalAlignment = -4108
1492
+
1493
+ # 水平对齐:
1494
+ # -4108:居中
1495
+ # -4131:左对齐
1496
+ # -4152:右对齐
1497
+
1498
+ # 垂直对齐:
1499
+ # -4108:居中
1500
+ # -4160:顶部对齐
1501
+ # -4107:底部对齐
1502
+
1503
+ add_borders(sheet)
1504
+
1505
+ # 获取第一行和第二行
1506
+ rows = sheet.range('1:2')
1507
+ # 设置字体名称
1508
+ rows.font.name = '微软雅黑'
1509
+ # 设置字体大小
1510
+ rows.font.size = 11
1511
+ # 设置字体加粗
1512
+ rows.font.bold = True
1513
+ # 设置标题栏字体颜色与背景色
1514
+ rows.color = (252, 228, 214)
1515
+ # 设置行高
1516
+ rows.row_height = 30
1517
+
1518
+ 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):
1519
+ last_month = TimeUtils.get_last_month()
1520
+
1521
+ supplierName = ''
1522
+
1523
+ excel_path_month = str(self.config.excel_shein_finance_month_report).replace('#store_name#', store_name)
1524
+
1525
+ month_data = [[
1526
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '利润', '售出数量', '售出金额', '售出成本', '添加时间',
1527
+ '业务单号', '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', 'SKU图片',
1528
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']]
1529
+ log('len(ledger_list)', len(ledger_list))
1530
+ for month_item in ledger_list:
1531
+ row_item = []
1532
+ supplierName = month_item['supplierName']
1533
+ platform_sku = month_item['sku']
1534
+ row_item.append(platform_sku)
1535
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1536
+ row_item.append(supplier_sku)
1537
+ row_item.append(month_item['suffixZh'])
1538
+ row_item.append(month_item['quantity'])
1539
+ row_item.append(month_item['cost'])
1540
+ row_item.append(month_item['amount'])
1541
+ row_item.append(month_item['cost_price'])
1542
+ row_item.append('')
1543
+ row_item.append(
1544
+ month_item['quantity'] if month_item['cost_price'] and month_item['settleTypeName'] == '收入结算' else 0)
1545
+ row_item.append(
1546
+ month_item['amount'] if month_item['cost_price'] and month_item['settleTypeName'] == '收入结算' else 0)
1547
+ row_item.append('')
1548
+ row_item.append(month_item['addTime'])
1549
+ row_item.append(month_item['businessNo'])
1550
+ row_item.append(month_item['billNo'])
1551
+ row_item.append(month_item['displayChangeTypeName'])
1552
+ row_item.append(month_item['settleTypeName'])
1553
+ row_item.append(month_item['skc'])
1554
+ row_item.append(month_item['supplierCode'])
1555
+ row_item.append(month_item['supplierName'])
1556
+ row_item.append(month_item['sku_img'])
1557
+ month_data.append(row_item)
1558
+
1559
+ sheet_name = f'{last_month}月销售明细'
1560
+
1561
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 1, 2, False), ['L'])
1562
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1563
+ set_title_style(sheet, 2)
1564
+ set_body_style(sheet, 3)
1565
+ add_borders(sheet)
1566
+ format_to_money(sheet, ['单价', '金额', '利润'])
1567
+ format_to_datetime(sheet, ['时间'])
1568
+ add_formula_for_column(sheet, '利润', '=IF(AND(ISNUMBER(G3),P3="收入结算"),F3-G3*D3,0)', 3)
1569
+ add_formula_for_column(sheet, '售出数量', '=IF(AND(ISNUMBER(G3),P3="收入结算"),D3,0)', 3)
1570
+ add_formula_for_column(sheet, '售出金额', '=IF(AND(ISNUMBER(G3),P3="收入结算"),F3,0)', 3)
1571
+ add_formula_for_column(sheet, '售出成本', '=IF(AND(ISNUMBER(G3),P3="收入结算"),D3 * G3,0)', 3)
1572
+ add_sum_for_cell(sheet, ['数量', '金额', '利润', '售出数量', '售出金额', '售出成本'])
1573
+ column_to_left(sheet, ['平台SKU', '商家SKU', '属性集'])
1574
+ column_to_right(sheet, ['单价', '金额', '利润'])
1575
+ hidden_columns(sheet, ['SKU图片'])
1576
+ close_excel(app, wb)
1577
+
1578
+ summary_list = self.calc_month_sales_percentage(month_data)
1579
+
1580
+ sheet_name = f'{last_month}月销售占比'
1581
+
1582
+ write_data(excel_path_month, sheet_name, sort_by_column(summary_list, 6, 2))
1583
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1584
+ set_title_style(sheet, 2)
1585
+ set_body_style(sheet, 3)
1586
+ add_borders(sheet)
1587
+ format_to_money(sheet, ['金额', '利润'])
1588
+ format_to_percent(sheet, ['占比'])
1589
+ add_sum_for_cell(sheet, ['利润', '售出数量', '售出金额'])
1590
+ column_to_left(sheet, ['商家SKU'])
1591
+ column_to_right(sheet, ['金额', '利润'])
1592
+ InsertImageV2(sheet, ['SKU图片'], 'shein', 90, None, None, True, 3)
1593
+ close_excel(app, wb)
1594
+
1595
+ stock_data = [[
1596
+ '月份', 'SKC', '供方货号', '平台SKU', '商家SKU', '属性集', '期初库存数量', '期初库存金额', '入库数量',
1597
+ '入库金额', '出库数量', '出库金额', '期末库存数量', '期末库存金额', '单价成本', '出库成本总额',
1598
+ '希音仓成本总额', '出库利润', '出库利润率', '供应商名称', '店铺账号', '店铺别名'
1599
+ ], [
1600
+ '合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
1601
+ ]]
1602
+
1603
+ for stock_item in shein_stock_list:
1604
+ row_item = []
1605
+ row_item.append(stock_item['reportDate'])
1606
+ row_item.append(stock_item['skc'])
1607
+ row_item.append(stock_item['supplierCode'])
1608
+ row_item.append(stock_item['skuCode'])
1609
+ row_item.append(stock_item['supplierSku'])
1610
+ row_item.append(stock_item['suffixZh'])
1611
+ row_item.append(stock_item['beginBalanceCnt'])
1612
+ row_item.append(stock_item['beginBalanceAmount'])
1613
+ row_item.append(stock_item['inCnt'])
1614
+ row_item.append(stock_item['inAmount'])
1615
+ row_item.append(stock_item['outCnt'])
1616
+ row_item.append(stock_item['outAmount'])
1617
+ row_item.append(stock_item['endBalanceCnt'])
1618
+ row_item.append(stock_item['endBalanceAmount'])
1619
+ row_item.append(stock_item['cost_price'])
1620
+ row_item.append('')
1621
+ row_item.append('')
1622
+ row_item.append('')
1623
+ row_item.append('')
1624
+ row_item.append(stock_item['supplierName'])
1625
+ row_item.append(store_username)
1626
+ row_item.append(store_name)
1627
+ stock_data.append(row_item)
1628
+
1629
+ sheet_name = f'{last_month}月库存结余'
1630
+ 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])
1631
+
1632
+ write_data(excel_path_month, sheet_name, sort_by_column(stock_data, 11, 2))
1633
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1634
+ set_title_style(sheet, 2)
1635
+ set_body_style(sheet, 3)
1636
+ add_borders(sheet)
1637
+ format_to_money(sheet, ['金额', '总额', '成本', '出库利润'])
1638
+ column_to_right(sheet, ['金额', '总额', '成本', '出库利润'])
1639
+ format_to_percent(sheet, ['利润率'])
1640
+ column_to_left(sheet, ['供方货号', '平台SKU', '商家SKU', '属性集'])
1641
+ add_sum_for_cell(sheet, ['期初库存数量', '期初库存金额', '入库数量', '入库金额', '出库数量', '出库金额', '期末库存数量', '期末库存金额', '出库成本总额', '希音仓成本总额', '出库利润'])
1642
+ add_formula_for_column(sheet, '出库成本总额', '=K3*O3', 3)
1643
+ add_formula_for_column(sheet, '希音仓成本总额', '=M3*O3', 3)
1644
+ add_formula_for_column(sheet, '出库利润', '=L3-P3', 3)
1645
+ add_formula_for_column(sheet, '出库利润率', '=IF(L3 > 0,R3/L3,0)', 3)
1646
+ sheet.autofit()
1647
+ close_excel(app, wb)
1648
+
1649
+ replenish_data = [[
1650
+ "补扣款单号", "款项类型", "补扣款分类", "对单类型", "关联单据", "单价", "数量", "总金额", "币种", "创建时间",
1651
+ "单据状态", "关联报账单", "拒绝原因", "确认/拒绝时间", "操作人", "会计日期", "是否可报账", "申诉单号",
1652
+ "公司主体", "出口模式", "备注", "供货商名称", "店铺账号", "店铺别名"
1653
+ ], [
1654
+ "合计", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
1655
+ ]]
1656
+
1657
+ for replenish_item in shein_replenish_list:
1658
+ row_item = []
1659
+ row_item.append(replenish_item['replenishNo'])
1660
+ row_item.append(replenish_item['replenishTypeName'])
1661
+ row_item.append(replenish_item['categoryName'])
1662
+ row_item.append(replenish_item['toOrderTypeName'])
1663
+ row_item.append(replenish_item['relationNo'])
1664
+ row_item.append(replenish_item['unitPrice'])
1665
+ row_item.append(replenish_item['quantity'])
1666
+ row_item.append(replenish_item['amount'])
1667
+ row_item.append(replenish_item['currencyCode'])
1668
+ row_item.append(replenish_item['addTime'])
1669
+ row_item.append(replenish_item['replenishStatusName'])
1670
+ row_item.append(replenish_item['reportOrderNo'])
1671
+ row_item.append(replenish_item['refuseReason'])
1672
+ row_item.append(replenish_item['decisionTime'])
1673
+ row_item.append(replenish_item['operator'])
1674
+ row_item.append(replenish_item['accountDate'])
1675
+ row_item.append(replenish_item['reportableName'])
1676
+ row_item.append(replenish_item['billNo'])
1677
+ row_item.append(replenish_item['companyName'])
1678
+ row_item.append(replenish_item['exportingModeName'])
1679
+ row_item.append(replenish_item['remark'])
1680
+ row_item.append(supplierName)
1681
+ row_item.append(store_username)
1682
+ row_item.append(store_name)
1683
+ replenish_data.append(row_item)
1684
+
1685
+ sheet_name = f'{last_month}月补扣款列表'
1686
+
1687
+ 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])
1688
+
1689
+ write_data(excel_path_month, sheet_name, sort_by_column(replenish_data, 2, 2))
1690
+
1691
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1692
+ set_title_style(sheet, 2)
1693
+ set_body_style(sheet, 3)
1694
+ add_borders(sheet)
1695
+ format_to_money(sheet, ['金额', '单价'])
1696
+ column_to_right(sheet, ['金额', '单价'])
1697
+ format_to_datetime(sheet, ['时间'])
1698
+ add_sum_for_cell(sheet, ['总金额'])
1699
+ sheet.autofit()
1700
+ close_excel(app, wb)
1701
+
1702
+ return_data = [[
1703
+ "退货单号", "退货计划单号", "处理类型", "发起原因", "说明", "状态", "退货方式", "退货仓库", "商家货号", "SKC",
1704
+ "待退货总数", "实际退货/报废总数", "签收时间", "创建时间", "运单号", "退货联系人", "联系人手机号", "退货地址"
1705
+ ], [
1706
+ "合计", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
1707
+ ]]
1708
+
1709
+ if len(shein_return_list) > 0:
1710
+ log(shein_return_list)
1711
+ for return_item in shein_return_list:
1712
+ row_item = []
1713
+ log(return_item)
1714
+ row_item.append(return_item['returnOrderNo'])
1715
+ row_item.append(return_item['returnPlanNo'])
1716
+ row_item.append(return_item['returnOrderTypeName'])
1717
+ row_item.append(return_item['returnReasonTypeName'])
1718
+ row_item.append(return_item['returnReason'])
1719
+ row_item.append(return_item['returnOrderStatusName'])
1720
+ row_item.append(return_item['returnWayTypeName'])
1721
+ row_item.append(return_item['warehouseName'])
1722
+ row_item.append(','.join(return_item['supplierCodeList']))
1723
+ row_item.append(','.join(return_item['skcNameList']))
1724
+ row_item.append(return_item['waitReturnQuantity'])
1725
+ row_item.append(return_item['returnQuantity'])
1726
+ row_item.append(return_item['signTime'])
1727
+ row_item.append(return_item['addTime'])
1728
+ row_item.append(return_item['expressNoList'])
1729
+ row_item.append(return_item['sellerContract'])
1730
+ row_item.append(return_item['sellerContractPhone'])
1731
+ row_item.append(return_item['returnAddress'])
1732
+ return_data.append(row_item)
1733
+
1734
+ sheet_name = f'{last_month}月退货与报废单列表'
1735
+
1736
+ write_data(excel_path_month, sheet_name, return_data, ['O', 'Q'])
1737
+
1738
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1739
+ set_title_style(sheet, 2)
1740
+ set_body_style(sheet, 3)
1741
+ add_borders(sheet)
1742
+ format_to_datetime(sheet, ['时间'])
1743
+ add_sum_for_cell(sheet, ['实际退货/报废总数'])
1744
+ sheet.autofit()
1745
+ close_excel(app, wb)
1746
+
1747
+ ###############################退供#######################################
1748
+ month_data = [[
1749
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '成本总额', '添加时间', '业务单号',
1750
+ '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', '店铺账号', '店铺别名', 'SKU图片',
1751
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']]
1752
+ log('len(back_list)', len(shein_back_list))
1753
+ for month_item in shein_back_list:
1754
+ row_item = []
1755
+ platform_sku = month_item['sku']
1756
+ row_item.append(platform_sku)
1757
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1758
+ row_item.append(supplier_sku)
1759
+ row_item.append(month_item['suffixZh'])
1760
+ row_item.append(month_item['quantity'])
1761
+ row_item.append(month_item['cost'])
1762
+ row_item.append(month_item['amount'])
1763
+ row_item.append(month_item['cost_price'])
1764
+ row_item.append('')
1765
+ row_item.append(month_item['addTime'])
1766
+ row_item.append(month_item['businessNo'])
1767
+ row_item.append(month_item['billNo'])
1768
+ row_item.append(month_item['displayChangeTypeName'])
1769
+ row_item.append(month_item['settleTypeName'])
1770
+ row_item.append(month_item['skc'])
1771
+ row_item.append(month_item['supplierCode'])
1772
+ row_item.append(month_item['supplierName'])
1773
+ row_item.append(store_username)
1774
+ row_item.append(store_name)
1775
+ row_item.append(month_item['sku_img'])
1776
+ month_data.append(row_item)
1777
+
1778
+ sheet_name = f'{last_month}月退供明细'
1779
+ 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])
1780
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 2, 2))
1781
+
1782
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1783
+ set_title_style(sheet, 2)
1784
+ set_body_style(sheet, 3)
1785
+ add_borders(sheet)
1786
+ format_to_money(sheet, ['金额', '单价', '总额'])
1787
+ column_to_right(sheet, ['金额', '单价', '总额'])
1788
+ format_to_datetime(sheet, ['时间'])
1789
+ add_sum_for_cell(sheet, ['数量', '金额', '成本总额'])
1790
+ add_formula_for_column(sheet, '成本总额', '=G3*D3', 3)
1791
+ hidden_columns(sheet, ['SKU图片'])
1792
+ sheet.autofit()
1793
+ close_excel(app, wb)
1794
+
1795
+ ###############################不结算#######################################
1796
+ month_data = [[
1797
+ '平台SKU', '商家SKU', '属性集', '数量', '单价', '金额', '单价成本', '成本总额', '添加时间', '业务单号',
1798
+ '单据号', '变动类型', '结算类型', 'SKC', '供方货号', '供应商名称', '店铺账号', '店铺别名', 'SKU图片',
1799
+ ], ['合计', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
1800
+ ]]
1801
+ log('len(shein_no_settlement_list)', len(shein_no_settlement_list))
1802
+ for month_item in shein_no_settlement_list:
1803
+ row_item = []
1804
+ platform_sku = month_item['sku']
1805
+ row_item.append(platform_sku)
1806
+ supplier_sku = month_item['supplierSku'] if month_item['supplierSku'] else '-'
1807
+ row_item.append(supplier_sku)
1808
+ row_item.append(month_item['suffixZh'])
1809
+ row_item.append(month_item['quantity'])
1810
+ row_item.append(month_item['cost'])
1811
+ row_item.append(month_item['amount'])
1812
+ row_item.append(month_item['cost_price'])
1813
+ row_item.append('')
1814
+ row_item.append(month_item['addTime'])
1815
+ row_item.append(month_item['businessNo'])
1816
+ row_item.append(month_item['billNo'])
1817
+ row_item.append(month_item['displayChangeTypeName'])
1818
+ row_item.append(month_item['settleTypeName'])
1819
+ row_item.append(month_item['skc'])
1820
+ row_item.append(month_item['supplierCode'])
1821
+ row_item.append(month_item['supplierName'])
1822
+ row_item.append(store_username)
1823
+ row_item.append(store_name)
1824
+ row_item.append(month_item['sku_img'])
1825
+ month_data.append(row_item)
1826
+
1827
+ sheet_name = f'{last_month}月不结算明细'
1828
+
1829
+ 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])
1830
+
1831
+ write_data(excel_path_month, sheet_name, sort_by_column(month_data, 2, 2))
1832
+
1833
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1834
+ set_title_style(sheet, 2)
1835
+ set_body_style(sheet, 3)
1836
+ add_borders(sheet)
1837
+ format_to_money(sheet, ['金额', '单价', '总额'])
1838
+ column_to_right(sheet, ['金额', '单价', '总额'])
1839
+ format_to_datetime(sheet, ['时间'])
1840
+ add_sum_for_cell(sheet, ['数量', '金额', '成本总额'])
1841
+ add_formula_for_column(sheet, '成本总额', '=G3*D3', 3)
1842
+ hidden_columns(sheet, ['SKU图片'])
1843
+ sheet.autofit()
1844
+ close_excel(app, wb)
1845
+
1846
+ sheet_name = f'{last_month}月利润汇总'
1847
+ # 建立利润汇总sheet页
1848
+ write_json_to_excel('excel_json_profit_detail.json', excel_path_month, sheet_name)
1849
+
1850
+ # 填入数据 销售数量
1851
+ app, wb, sheet = open_excel(excel_path_month, sheet_name)
1852
+ delete_sheet_if_exists(wb, 'Sheet1')
1853
+ move_sheet_to_position(wb, sheet_name, 1)
1854
+ wb.save()
1855
+ sheet.activate()
1856
+
1857
+ target_month = find_column_by_data(sheet, 2, last_month)
1858
+ sheet.range(f'{target_month}3').value = f"='{last_month}月销售明细'!I2"
1859
+
1860
+ sheet.range(f'{target_month}4').number_format = f"¥#,##0.00;¥-#,##0.00"
1861
+ sheet.range(f'{target_month}4').value = f"='{last_month}月销售明细'!J2"
1862
+
1863
+ sheet.range(f'A5').value = f"销售成本"
1864
+ sheet.range(f'{target_month}5').number_format = f"¥#,##0.00;¥-#,##0.00"
1865
+ sheet.range(f'{target_month}5').value = f"='{last_month}月销售明细'!K2"
1866
+
1867
+ sheet.range(f'A6').value = f"销售利润"
1868
+ sheet.range(f'{target_month}6').number_format = f"¥#,##0.00;¥-#,##0.00"
1869
+ sheet.range(f'{target_month}6').value = f"='{last_month}月销售明细'!H2"
1870
+
1871
+ # sheet.range(f'{target_month}6').number_format = f"¥#,##0.00;¥-#,##0.00"
1872
+ # sheet.range(f'{target_month}6').value = f"=-'{last_month}月退货与报废单列表'!L2 * 3"
1873
+ sheet.range(f'{target_month}7').number_format = f"¥#,##0.00;¥-#,##0.00"
1874
+ sheet.range(f'{target_month}7').value = f"=-'{last_month}月补扣款列表'!H2"
1875
+ sheet.range(f'{target_month}8').number_format = f"¥#,##0.00;¥-#,##0.00"
1876
+ sheet.range(f'{target_month}9').number_format = f"¥#,##0.00;¥-#,##0.00"
1877
+ sheet.range(f'{target_month}9').value = f"=SUM({target_month}6:{target_month}8)"
1878
+ sheet.range(f'{target_month}10').number_format = f"¥#,##0.00;¥-#,##0.00"
1879
+ sheet.range(f'{target_month}10').value = f"='{last_month}月库存结余'!Q2"
1880
+
1881
+ sheet.range('A1').value = f'2025年{last_month}月 shein 利润汇总表 {store_name}'
1882
+ sheet.range(f'{target_month}:{target_month}').autofit()
1883
+ wb.save()
1884
+ close_excel(app, wb)
1885
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path_month)
1886
+
1887
+ def write_summary_algorithm_1(self):
1888
+ excel_path = self.config.excel_shein_finance_month_report_summary
1889
+
1890
+ sheet_name = '总表-算法1'
1891
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
1892
+ total_data = []
1893
+ header = ['店铺账号', '店铺别名']
1894
+ for mall_id, excel_data in dict_store.items():
1895
+ total_data += [[mall_id, excel_data]]
1896
+
1897
+ log(total_data)
1898
+ filtered_value = [header] + total_data
1899
+ log(filtered_value)
1900
+ filtered_value = add_suffixed_column(filtered_value, '店长', '')
1901
+ filtered_value = add_suffixed_column(filtered_value, '出库金额', '')
1902
+ filtered_value = add_suffixed_column(filtered_value, '出库成本', '')
1903
+ filtered_value = add_suffixed_column(filtered_value, '不结算金额', '')
1904
+ filtered_value = add_suffixed_column(filtered_value, '不结算成本', '')
1905
+ filtered_value = add_suffixed_column(filtered_value, '实际出库金额', '')
1906
+ filtered_value = add_suffixed_column(filtered_value, '实际出库成本', '')
1907
+ filtered_value = add_suffixed_column(filtered_value, '补扣款', '')
1908
+ filtered_value = add_suffixed_column(filtered_value, '线下运费', '')
1909
+ filtered_value = add_suffixed_column(filtered_value, '侵权扣款', '')
1910
+ filtered_value = add_suffixed_column(filtered_value, '希音仓成本总额', '')
1911
+ filtered_value = add_suffixed_column(filtered_value, '毛利', '')
1912
+
1913
+ # 匹配店铺店长
1914
+ dict_store_manager_shein = self.config.shein_store_manager
1915
+ for row in filtered_value:
1916
+ mall_name = row[0]
1917
+ if mall_name == '店铺账号':
1918
+ continue
1919
+ row[2] = dict_store_manager_shein.get(str(mall_name).lower())
1920
+ self.write_to_one(filtered_value, excel_path, sheet_name)
1921
+
1922
+ def write_summary_algorithm_2(self):
1923
+ excel_path = self.config.excel_shein_finance_month_report_summary
1924
+
1925
+ app, wb, sheet = open_excel(excel_path, 2)
1926
+
1927
+ sheet_name = '总表-算法2'
1928
+ # 将目标工作表移动到第一个工作表之前
1929
+ sheet.api.Move(Before=wb.sheets[0].api)
1930
+ wb.save()
1931
+ close_excel(app, wb)
1932
+
1933
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
1934
+ total_data = []
1935
+ header = ['店铺账号', '店铺别名']
1936
+ for mall_id, excel_data in dict_store.items():
1937
+ total_data += [[mall_id, excel_data]]
1938
+
1939
+ filtered_value = [header] + total_data
1940
+ filtered_value = add_suffixed_column(filtered_value, '店长', '')
1941
+ filtered_value = add_suffixed_column(filtered_value, '出库金额', '')
1942
+ filtered_value = add_suffixed_column(filtered_value, '出库成本', '')
1943
+ filtered_value = add_suffixed_column(filtered_value, '退供金额', '')
1944
+ filtered_value = add_suffixed_column(filtered_value, '退供成本', '')
1945
+ filtered_value = add_suffixed_column(filtered_value, '实际出库金额', '')
1946
+ filtered_value = add_suffixed_column(filtered_value, '实际出库成本', '')
1947
+ filtered_value = add_suffixed_column(filtered_value, '补扣款', '')
1948
+ filtered_value = add_suffixed_column(filtered_value, '线下运费', '')
1949
+ filtered_value = add_suffixed_column(filtered_value, '侵权扣款', '')
1950
+ filtered_value = add_suffixed_column(filtered_value, '希音仓成本总额', '')
1951
+ filtered_value = add_suffixed_column(filtered_value, '毛利', '')
1952
+
1953
+ # 匹配店铺店长
1954
+ dict_store_manager_shein = self.config.shein_store_manager
1955
+ for row in filtered_value:
1956
+ mall_name = row[0]
1957
+ if mall_name == '店铺账号':
1958
+ continue
1959
+ row[2] = dict_store_manager_shein.get(str(mall_name).lower())
1960
+ self.write_to_one(filtered_value, excel_path, sheet_name)
1961
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
1962
+
1963
+ def sumary_part(self):
1964
+ excel_path = self.config.excel_shein_finance_month_report_summary
1965
+ src_directory = f'{self.config.auto_dir}/shein/cache'
1966
+ for file in os.listdir(src_directory):
1967
+ # 检查是否为文件且符合命名模式
1968
+ if file.startswith(f"sheet_{TimeUtils.get_last_month()}") and file.endswith(".json"):
1969
+ file_path = os.path.join(src_directory, file)
1970
+ filename = os.path.basename(file_path) # 获取 "tool.py"
1971
+ name = os.path.splitext(filename)[0]
1972
+ sheet_name = name.split('_')[2]
1973
+ dict = read_dict_from_file(file_path)
1974
+ total_data = []
1975
+ header = []
1976
+ for mall_id, excel_data in dict.items():
1977
+ header = excel_data[0]
1978
+ if len(excel_data) > 1:
1979
+ total_data += excel_data[1:]
1980
+
1981
+ filtered_value = [header] + total_data
1982
+ self.write_to_one(filtered_value, excel_path, f'{sheet_name}-汇总-{TimeUtils.get_last_month()}月')
1983
+
1984
+ def write_to_one(self, data, excel_path, sheet_name="Sheet1", header_column=None):
1985
+ write_data(excel_path, sheet_name, data)
1986
+ app, wb, sheet = open_excel(excel_path, sheet_name)
1987
+ add_borders(sheet)
1988
+ format_to_money(sheet, ['金额', '成本'])
1989
+ format_to_datetime(sheet, ['时间'])
1990
+ if '库存结余' in sheet_name:
1991
+ format_to_percent(sheet, ['利润率'])
1992
+ format_to_month(sheet, ['月份'])
1993
+ add_formula_for_column(sheet, '出库成本总额', f'=IF(ISNUMBER(O2),K2*O2,0)', 2)
1994
+ add_formula_for_column(sheet, '希音仓成本总额', f'=IF(ISNUMBER(O2),M2*O2,0)', 2)
1995
+ add_formula_for_column(sheet, '出库利润', f'=L2-P2', 2)
1996
+ add_formula_for_column(sheet, '出库利润率', f'=IF(L2 > 0,R2/L2,0)', 2)
1997
+ if '退供列表' in sheet_name:
1998
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER(G2),D2*G2,0)', 2)
1999
+ if '不结算列表' in sheet_name:
2000
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER(G2),D2*G2,0)', 2)
2001
+ if '总表-算法1' in sheet_name:
2002
+ format_to_money(sheet, ['补扣款', '线下运费', '侵权扣款', '毛利'])
2003
+ add_formula_for_column(sheet, '出库金额',
2004
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!L:L)",
2005
+ 2)
2006
+ add_formula_for_column(sheet, '出库成本',
2007
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!P:P)",
2008
+ 2)
2009
+ add_formula_for_column(sheet, '不结算金额',
2010
+ f"=SUMIF('不结算列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'不结算列表-汇总-{TimeUtils.get_last_month()}月'!F:F)",
2011
+ 2)
2012
+ add_formula_for_column(sheet, '不结算成本',
2013
+ f"=SUMIF('不结算列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'不结算列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2014
+ 2)
2015
+ add_formula_for_column(sheet, '实际出库金额', f"=D2-F2", 2)
2016
+ add_formula_for_column(sheet, '实际出库成本', f"=E2-G2", 2)
2017
+ add_formula_for_column(sheet, '补扣款',
2018
+ f"=SUMIF('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法1'!A:A,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2019
+ 2)
2020
+ add_formula_for_column(sheet, '希音仓成本总额',
2021
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!Q:Q)",
2022
+ 2)
2023
+ add_formula_for_column(sheet, '毛利', f"=H2-I2-J2-K2-L2", 2)
2024
+ # 全是公式 无法排序
2025
+
2026
+ if '总表-算法2' in sheet_name:
2027
+ format_to_money(sheet, ['补扣款', '线下运费', '侵权扣款', '毛利'])
2028
+ add_formula_for_column(sheet, '出库金额',
2029
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!L:L)",
2030
+ 2)
2031
+ add_formula_for_column(sheet, '出库成本',
2032
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!P:P)",
2033
+ 2)
2034
+ add_formula_for_column(sheet, '退供金额',
2035
+ f"=SUMIF('退供列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'退供列表-汇总-{TimeUtils.get_last_month()}月'!F:F)",
2036
+ 2)
2037
+ add_formula_for_column(sheet, '退供成本',
2038
+ f"=SUMIF('退供列表-汇总-{TimeUtils.get_last_month()}月'!Q:Q,'总表-算法1'!A:A,'退供列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2039
+ 2)
2040
+ add_formula_for_column(sheet, '实际出库金额', f"=D2-F2", 2)
2041
+ add_formula_for_column(sheet, '实际出库成本', f"=E2-G2", 2)
2042
+ add_formula_for_column(sheet, '补扣款',
2043
+ f"=SUMIF('补扣款列表-汇总-{TimeUtils.get_last_month()}月'!W:W,'总表-算法1'!A:A,'补扣款列表-汇总-{TimeUtils.get_last_month()}月'!H:H)",
2044
+ 2)
2045
+ add_formula_for_column(sheet, '希音仓成本总额',
2046
+ f"=SUMIF('库存结余-汇总-{TimeUtils.get_last_month()}月'!U:U,'总表-算法1'!A:A,'库存结余-汇总-{TimeUtils.get_last_month()}月'!Q:Q)",
2047
+ 2)
2048
+ add_formula_for_column(sheet, '毛利', f"=H2-I2-J2-K2-L2", 2)
2049
+
2050
+ move_sheet_to_position(wb, '总表-算法1', 1)
2051
+ move_sheet_to_position(wb, '总表-算法2', 1)
2052
+
2053
+ set_title_style(sheet, 1)
2054
+ wb.save()
2055
+ close_excel(app, wb)
2056
+
2057
+ def format_funds(self, sheet):
2058
+ beautify_title(sheet)
2059
+ column_to_right(sheet, ['金额', '汇总'])
2060
+ format_to_money(sheet, ['金额', '汇总'])
2061
+ add_sum_for_cell(sheet, ['在途商品金额', '在仓商品金额', '待结算金额', '可提现金额', '销售出库金额', '汇总'])
2062
+ add_formula_for_column(sheet, '汇总', '=SUM(D3:G3)', 3)
2063
+ sheet.autofit()
2064
+
2065
+ def format_bad_comment(self, sheet):
2066
+ beautify_title(sheet)
2067
+ column_to_left(sheet, ['商品信息'])
2068
+ autofit_column(sheet, ['买家评价', '时间信息', '标签关键词'])
2069
+ specify_column_width(sheet, ['买家评价', '商品信息'], 150 / 6)
2070
+ color_for_column(sheet, ['买家评分'], '红色')
2071
+ colorize_by_field(sheet, 'skc')
2072
+ add_borders(sheet)
2073
+ InsertImageV2(sheet, ['商品图片', '图1', '图2', '图3', '图4', '图5'])
2074
+
2075
+ def write_bad_comment(self):
2076
+ excel_path = create_file_path(self.config.excel_bad_comment)
2077
+ header = ['评价ID', '商品图片', '商品信息', '买家评分', '买家评价', '标签关键词', '区域', '时间信息', '有图', '图1',
2078
+ '图2', '图3', '图4', '图5', 'skc']
2079
+ summary_excel_data = [header]
2080
+
2081
+ cache_file = f'{self.config.auto_dir}/shein/dict/comment_list_{TimeUtils.today_date()}.json'
2082
+ dict = read_dict_from_file(cache_file)
2083
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2084
+
2085
+ for store_username, comment_list in dict.items():
2086
+ store_name = dict_store.get(store_username)
2087
+ sheet_name = store_name
2088
+
2089
+ store_excel_data = [header]
2090
+ for comment in comment_list:
2091
+ row_item = []
2092
+ row_item.append(f'{comment['commentId']}\n{store_name}')
2093
+ row_item.append(comment['goodsThumb'])
2094
+ product_info = f'属性:{comment["goodsAttribute"]}\n货号:{comment["goodSn"]}\nSPU:{comment["spu"]}\nSKC:{comment["skc"]}\nSKU:{comment["sku"]}'
2095
+ row_item.append(product_info)
2096
+ row_item.append(calculate_star_symbols(comment['goodsCommentStar']))
2097
+ row_item.append(comment['goodsCommentContent'])
2098
+ qualityLabel = '存在质量问题\n' if comment['isQualityLabel'] == 1 else ''
2099
+ bad_comment_label = qualityLabel + '\n'.join([item['labelName'] for item in comment['badCommentLabelList']])
2100
+
2101
+ row_item.append(bad_comment_label)
2102
+ row_item.append(comment['dataCenterName'])
2103
+ time_info = f'下单时间:{comment["orderTime"]}\n评论时间:{comment["commentTime"]}'
2104
+ row_item.append(time_info)
2105
+
2106
+ # 获取图片数量
2107
+ image_num = len(comment.get('goodsCommentImages', []))
2108
+ # 设置imgFlag值(如果comment中没有imgFlag字段,默认设为0)
2109
+ imgFlag = image_num if comment.get('imgFlag') == 1 else 0
2110
+ row_item.append(imgFlag)
2111
+
2112
+ images = comment.get('goodsCommentImages', [])
2113
+ for i in range(5):
2114
+ row_item.append(images[i] if i < len(images) else '')
2115
+
2116
+ row_item.append(comment['skc'])
2117
+
2118
+ store_excel_data.append(row_item)
2119
+ summary_excel_data.append(row_item)
2120
+
2121
+ # write_data(excel_path, sheet_name, store_excel_data)
2122
+ # format_bad_comment(excel_path, sheet_name)
2123
+
2124
+ sheet_name = 'Sheet1'
2125
+
2126
+ batch_excel_operations(excel_path, [
2127
+ (sheet_name, 'write', summary_excel_data),
2128
+ (sheet_name, 'format', self.format_bad_comment),
2129
+ ])
2130
+
2131
+ def write_funds(self):
2132
+ cache_file = f'{self.config.auto_dir}/shein/cache/stat_fund_{TimeUtils.today_date()}.json'
2133
+ dict = read_dict_from_file(cache_file)
2134
+ data = []
2135
+ for key, val in dict.items():
2136
+ data.append(val)
2137
+
2138
+ excel_path = create_file_path(self.config.excel_shein_fund)
2139
+ sheet_name = 'Sheet1'
2140
+ data.insert(0, ['汇总', '', '', '', '', '', '', '', '', ''])
2141
+ data.insert(0, ['店铺名称', '店铺账号', '店长', '在途商品金额', '在仓商品金额', '待结算金额', '可提现金额',
2142
+ '销售出库金额', '汇总', '导出时间'])
2143
+ batch_excel_operations(excel_path, [
2144
+ ('Sheet1', 'write', sort_by_column(data, 7, 2)),
2145
+ ('Sheet1', 'format', self.format_funds),
2146
+ ])
2147
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2148
+
2149
+ def format_skc_quality(self, sheet):
2150
+ beautify_title(sheet)
2151
+ colorize_by_field(sheet, 'skc')
2152
+ add_borders(sheet)
2153
+ InsertImageV2(sheet, ['商品图片'])
2154
+
2155
+ def sort_site_desc_by_sale_cnt_14d(self, data, reverse=True):
2156
+ """
2157
+ 对data中的site_desc_vo_list按照skc_site_sale_cnt_14d进行排序
2158
+
2159
+ 参数:
2160
+ data: 包含site_desc_vo_list的字典
2161
+ reverse: 是否倒序排序,默认为True(从大到小)
2162
+
2163
+ 返回:
2164
+ 排序后的data(原数据会被修改)
2165
+ """
2166
+ if 'site_desc_vo_list' in data and isinstance(data['site_desc_vo_list'], list):
2167
+ # 处理None值,将它们放在排序结果的最后
2168
+ data['site_desc_vo_list'].sort(
2169
+ key=lambda x: float('-inf') if x.get('skc_site_sale_cnt_14d') is None else x['skc_site_sale_cnt_14d'],
2170
+ reverse=reverse
2171
+ )
2172
+ return data
2173
+
2174
+ def write_skc_quality_estimate(self):
2175
+ excel_path = create_file_path(self.config.excel_skc_quality_estimate)
2176
+ header = ['店铺信息', '商品图片', '统计日期', '国家', '当日销量', '14日销量', '14日销量占比', '质量等级',
2177
+ '客评数/客评分', '差评数/差评率', '退货数/退货率', 'skc', 'skc当日销量', 'skc14日销量', 'skc14日销量占比']
2178
+ summary_excel_data = [header]
2179
+
2180
+ stat_date = TimeUtils.before_yesterday()
2181
+ cache_file = f'{self.config.auto_dir}/shein/dict/googs_estimate_{stat_date}.json'
2182
+ dict = read_dict_from_file(cache_file)
2183
+ if len(dict) == 0:
2184
+ log('昨日质量评估数据不存在')
2185
+ return
2186
+
2187
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2188
+
2189
+ operations = []
2190
+ for store_username, skc_list in dict.items():
2191
+ store_name = dict_store.get(store_username)
2192
+ sheet_name = store_name
2193
+
2194
+ store_excel_data = [header]
2195
+ for skc_item in skc_list:
2196
+ sorted_skc_item = self.sort_site_desc_by_sale_cnt_14d(skc_item, True)
2197
+ # for site in sorted_skc_item['site_desc_vo_list']:
2198
+ # print(f"{site['country_site']}: {site['skc_site_sale_cnt_14d']}")
2199
+ # continue
2200
+ store_info = f'{store_name}'
2201
+ skc = sorted_skc_item['skc']
2202
+ sites = sorted_skc_item['site_desc_vo_list']
2203
+ skc_sale_cnt = sorted_skc_item['skc_sale_cnt']
2204
+ skc_sale_cnt_14d = sorted_skc_item['skc_sale_cnt_14d']
2205
+ skc_sale_rate_14d = sorted_skc_item['skc_sale_rate_14d']
2206
+ for site in sites:
2207
+ row_item = []
2208
+ row_item.append(store_info)
2209
+ row_item.append(skc_item['goods_image'])
2210
+ row_item.append(stat_date)
2211
+ row_item.append(site['country_site'])
2212
+ row_item.append(site['skc_site_sale_cnt'])
2213
+ cnt_14d = site['skc_site_sale_cnt_14d']
2214
+ if cnt_14d is None or cnt_14d <= 0:
2215
+ continue
2216
+ row_item.append(cnt_14d)
2217
+ row_item.append(site['skc_site_sale_rate_14d'])
2218
+ row_item.append(site['quality_level'])
2219
+ customer_info = f'{site["customer_evaluate_num"]}/{site["customer_evaluate_score"][:-1]}'
2220
+ row_item.append(customer_info)
2221
+ negative_info = f'{site["negative_quantity"]}/{site["negative_percent"]}'
2222
+ row_item.append(negative_info)
2223
+ return_info = f'{site["goods_return_quantity"]}/{site["goods_return_percent"]}'
2224
+ row_item.append(return_info)
2225
+ row_item.append(skc)
2226
+ row_item.append(skc_sale_cnt)
2227
+ row_item.append(skc_sale_cnt_14d)
2228
+ row_item.append(skc_sale_rate_14d)
2229
+ store_excel_data.append(row_item)
2230
+ summary_excel_data.append(row_item)
2231
+
2232
+ operations.append((
2233
+ sheet_name, 'write', store_excel_data
2234
+ ))
2235
+ operations.append((
2236
+ sheet_name, 'format', self.format_skc_quality
2237
+ ))
2238
+ operations.append((
2239
+ 'Sheet1', 'delete'
2240
+ ))
2241
+ batch_excel_operations(excel_path, operations)
2242
+
2243
+ # 添加月度sheet操作 - 自定义操作函数
2244
+ def write_monthly_data(self, sheet, data, name):
2245
+ # 写入数据到A5位置(月度数据从A列开始)
2246
+ sheet.range('A5').value = data
2247
+ # 设置标题
2248
+ sheet.range('A1').value = f'{name}SHEIN{TimeUtils.get_current_month()}月店铺数据'
2249
+
2250
+ def write_sales_data(self):
2251
+ yesterday = TimeUtils.get_yesterday()
2252
+ model = SheinStoreSalesDetailManager(self.config.database_url)
2253
+ records = model.get_one_day_records(yesterday, SheinStoreSalesDetail.sales_amount.desc())
2254
+ data_day = []
2255
+ dict_store_manager_shein = self.config.shein_store_manager
2256
+ dict_store_name = read_dict_from_file(self.config.shein_store_alias)
2257
+
2258
+ # 准备每日汇总数据
2259
+ for record in records:
2260
+ store_data = []
2261
+ store_data.append(dict_store_name.get(record.store_username))
2262
+ store_data.append(dict_store_manager_shein.get(str(record.store_username).lower(), '-'))
2263
+ store_data.append(record.sales_num)
2264
+ store_data.append(record.sales_num_inc)
2265
+ store_data.append(record.sales_amount)
2266
+ store_data.append(record.sales_amount_inc)
2267
+ store_data.append(record.visitor_num)
2268
+ store_data.append(record.visitor_num_inc)
2269
+ store_data.append(record.bak_A_num)
2270
+ store_data.append(record.bak_A_num_inc)
2271
+ store_data.append(record.new_A_num)
2272
+ store_data.append(record.new_A_num_inc)
2273
+ store_data.append(record.on_sales_product_num)
2274
+ store_data.append(record.on_sales_product_num_inc)
2275
+ store_data.append(record.wait_shelf_product_num)
2276
+ store_data.append(record.wait_shelf_product_num_inc)
2277
+ store_data.append(record.upload_product_num)
2278
+ store_data.append(record.upload_product_num_inc)
2279
+ store_data.append(record.sold_out_product_num)
2280
+ store_data.append(record.shelf_off_product_num)
2281
+ data_day.append(store_data)
2282
+
2283
+ excel_path = create_file_path(self.config.excel_daily_report)
2284
+ delete_file(excel_path)
2285
+ sheet_name_first = 'SHEIN销售部每日店铺情况'
2286
+
2287
+ # 准备批量操作列表
2288
+ base_operations = []
2289
+
2290
+ # 添加每日汇总sheet的操作 - 自定义操作函数
2291
+ def write_daily_data(sheet):
2292
+ # 写入数据到B5位置,保持原有格式
2293
+ sheet.range('B5').value = data_day
2294
+ # 设置标题
2295
+ sheet.range('A1').value = f'销售部SHEIN{TimeUtils.get_current_month()}月店铺数据'
2296
+ # 设置日期和合并
2297
+ sheet.range('A4').value = f'{TimeUtils.format_date_cross_platform(yesterday)}\n({TimeUtils.get_chinese_weekday(yesterday)})'
2298
+
2299
+ base_operations.append((sheet_name_first, 'format', write_daily_data))
2300
+ base_operations.append((sheet_name_first, 'format', self._format_daily_summary_sheet, yesterday, len(data_day)))
2301
+ base_operations.append((sheet_name_first, 'move', 1))
2302
+ base_operations.append(('Sheet1', 'delete'))
2303
+
2304
+ # 获取店铺列表并准备月度数据
2305
+ store_list = model.get_distinct_store_sales_list()
2306
+
2307
+ # 准备所有店铺的数据
2308
+ store_operations_data = []
2309
+ for store in store_list:
2310
+ store_username = store[0]
2311
+ store_name = dict_store_name.get(store_username)
2312
+ records = model.get_one_month_records(TimeUtils.get_current_year(), TimeUtils.get_current_month(), store_username)
2313
+
2314
+ data_month = []
2315
+ for record in records:
2316
+ store_data = []
2317
+ store_data.append(record.day)
2318
+ store_data.append(record.sales_num)
2319
+ store_data.append(record.sales_num_inc)
2320
+ store_data.append(record.sales_amount)
2321
+ store_data.append(record.sales_amount_inc)
2322
+ store_data.append(record.visitor_num)
2323
+ store_data.append(record.visitor_num_inc)
2324
+ store_data.append(record.bak_A_num)
2325
+ store_data.append(record.bak_A_num_inc)
2326
+ store_data.append(record.new_A_num)
2327
+ store_data.append(record.new_A_num_inc)
2328
+ store_data.append(record.on_sales_product_num)
2329
+ store_data.append(record.on_sales_product_num_inc)
2330
+ store_data.append(record.wait_shelf_product_num)
2331
+ store_data.append(record.wait_shelf_product_num_inc)
2332
+ store_data.append(record.upload_product_num)
2333
+ store_data.append(record.upload_product_num_inc)
2334
+ store_data.append(record.sold_out_product_num)
2335
+ store_data.append(record.shelf_off_product_num)
2336
+ # store_data.append(record.remark) # 月度数据不包含备注列,保持19列
2337
+ data_month.append(store_data)
2338
+
2339
+ store_operations_data.append((store_name, data_month))
2340
+
2341
+ # 构建所有操作列表
2342
+ operations = base_operations.copy()
2343
+
2344
+ # 添加店铺操作
2345
+ for store_name, data_month in store_operations_data:
2346
+ # 清理店铺名称
2347
+ clean_store_name = self._clean_sheet_name(store_name)
2348
+ operations.append((clean_store_name, 'format', self.write_monthly_data, data_month, clean_store_name))
2349
+ operations.append((clean_store_name, 'format', self._format_store_monthly_sheet, clean_store_name, len(data_month)))
2350
+
2351
+ # 添加最后激活操作
2352
+ operations.append((sheet_name_first, 'active'))
2353
+
2354
+ # 执行批量操作(内部会自动分批处理)
2355
+ success = batch_excel_operations(excel_path, operations)
2356
+
2357
+ if success:
2358
+ # 发送文件到企业微信
2359
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2360
+ log(f"销售数据写入完成: {excel_path}")
2361
+ else:
2362
+ log(f"销售数据写入失败: {excel_path}")
2363
+
2364
+ def _clean_sheet_name(self, name):
2365
+ """
2366
+ 清理工作表名称,移除Excel不支持的字符
2367
+ """
2368
+ if not name:
2369
+ return "DefaultSheet"
2370
+
2371
+ # Excel工作表名称限制:不能包含 [ ] : * ? / \ 字符,且长度不超过31字符
2372
+ invalid_chars = ['[', ']', ':', '*', '?', '/', '\\']
2373
+ clean_name = name
2374
+
2375
+ for char in invalid_chars:
2376
+ clean_name = clean_name.replace(char, '_')
2377
+
2378
+ # 限制长度为31字符
2379
+ if len(clean_name) > 31:
2380
+ clean_name = clean_name[:28] + "..."
2381
+
2382
+ # 确保不为空
2383
+ if not clean_name.strip():
2384
+ clean_name = "Sheet"
2385
+
2386
+ return clean_name
2387
+
2388
+ def _format_daily_summary_sheet(self, sheet, yesterday, data_length):
2389
+ """格式化每日汇总sheet"""
2390
+ las_row = data_length + 4 # 数据从第5行开始,4行header
2391
+
2392
+ # 设置数据区域格式(从B5开始,因为数据写入到B5)
2393
+ sheet.range(f'B5:U{las_row}').api.Font.Color = 0x000000
2394
+ sheet.range(f'B5:U{las_row}').api.Font.Bold = False
2395
+
2396
+ # 设置A4日期列的格式和合并
2397
+ sheet.range('A4').column_width = 16
2398
+ sheet.range('A4').api.VerticalAlignment = -4160 # 垂直顶部对齐
2399
+ sheet.range(f'A4:A{las_row}').merge()
2400
+
2401
+ # 设置负数为红色(E,G,I,K列)
2402
+ self._set_negative_numbers_red(sheet, ['E', 'G', 'I', 'K'], 5, las_row)
2403
+
2404
+ # 格式化表头
2405
+ self._format_daily_header(sheet, las_row)
2406
+
2407
+ # 设置汇总公式和格式
2408
+ self._set_summary_formulas(sheet, las_row)
2409
+
2410
+ # 设置边框
2411
+ self._set_borders(sheet, f'A2:U{las_row}')
2412
+
2413
+ sheet.autofit()
2414
+
2415
+ def _format_store_monthly_sheet(self, sheet, store_name, data_length):
2416
+ """格式化店铺月度sheet"""
2417
+ las_row = data_length + 4 # 数据从第5行开始,4行header
2418
+
2419
+ # 数据已经写入,现在进行格式化
2420
+ # 设置数据区域格式(从A5开始到S列,月度数据是19列)
2421
+ sheet.range(f'A5:S{las_row}').api.Font.Color = 0x000000
2422
+ sheet.range(f'A5:S{las_row}').api.Font.Bold = False
2423
+
2424
+ # 格式化表头
2425
+ self._format_monthly_header(sheet, las_row)
2426
+
2427
+ # 设置汇总公式和格式
2428
+ self._set_monthly_summary_formulas(sheet, las_row)
2429
+
2430
+ # 设置边框
2431
+ self._set_borders(sheet, f'A2:S{las_row}')
2432
+
2433
+ sheet.autofit()
2434
+
2435
+ def _set_negative_numbers_red(self, sheet, columns, start_row, end_row):
2436
+ """设置负数为红色"""
2437
+ for col in columns:
2438
+ column_range = sheet.range(f'{col}{start_row}:{col}{end_row}')
2439
+ for cell in column_range:
2440
+ if cell.value is not None and cell.value < 0:
2441
+ cell.font.color = (255, 0, 0)
2442
+
2443
+ def _format_daily_header(self, sheet, las_row):
2444
+ """格式化每日汇总表头,完全按照原始格式"""
2445
+ # 第一行:标题
2446
+ range_one = f'A1:U1'
2447
+ sheet.range(range_one).merge()
2448
+ sheet.range(range_one).api.Font.Size = 24
2449
+ sheet.range(range_one).api.Font.Bold = True
2450
+ sheet.range(range_one).api.HorizontalAlignment = -4108
2451
+ sheet.range(range_one).api.VerticalAlignment = -4108
2452
+
2453
+ # 第二行:分类标题
2454
+ range_two_part_1 = f'A2:C2'
2455
+ range_two_part_2 = f'D2:O2'
2456
+ range_two_part_3 = f'P2:U2'
2457
+ sheet.range(range_two_part_1).merge()
2458
+ sheet.range(range_two_part_2).merge()
2459
+ sheet.range(range_two_part_3).merge()
2460
+
2461
+ sheet.range(f'A2:C3').color = 0x47a100
2462
+
2463
+ sheet.range('D2').value = '店铺的结果和稳定性'
2464
+ sheet.range(range_two_part_2).api.Font.Size = 16
2465
+ sheet.range(range_two_part_2).api.Font.Color = 0xFFFFFF
2466
+ sheet.range(range_two_part_2).api.Font.Bold = True
2467
+ sheet.range(range_two_part_2).api.HorizontalAlignment = -4108
2468
+ sheet.range(range_two_part_2).api.VerticalAlignment = -4108
2469
+ sheet.range(f'D2:O3').color = 0x0000FF
2470
+
2471
+ sheet.range('P2').value = '上新的质量和数量'
2472
+ sheet.range(range_two_part_3).api.Font.Size = 16
2473
+ sheet.range(range_two_part_3).api.Font.Color = 0xFFFFFF
2474
+ sheet.range(range_two_part_3).api.Font.Bold = True
2475
+ sheet.range(range_two_part_3).api.HorizontalAlignment = -4108
2476
+ sheet.range(range_two_part_3).api.VerticalAlignment = -4108
2477
+ sheet.range(f'P2:U3').color = 0x47a100
2478
+
2479
+ # 第三行:列标题
2480
+ range_three = f'A3:U3'
2481
+ sheet.range('A3').value = ['日期', '店铺', '店长', '昨日单量', '对比前日', '昨日销售额', '对比前日', '昨日访客',
2482
+ '对比前天', '备货款A', '对比前日', '新款A', '对比前日', '在售商品', '对比前日', '待上架',
2483
+ '对比前日', '昨日上传', '对比前日', '已售罄', '已下架']
2484
+ sheet.range(range_three).api.Font.Size = 11
2485
+ sheet.range(range_three).api.Font.Color = 0xFFFFFF
2486
+ sheet.range(range_three).api.Font.Bold = True
2487
+ sheet.range(range_three).api.HorizontalAlignment = -4108
2488
+ sheet.range(range_three).api.VerticalAlignment = -4108
2489
+
2490
+ # 第四行:汇总行
2491
+ range_four = f'B4:U4'
2492
+ sheet.range('B4').value = '汇总'
2493
+ sheet.range('C4').value = '-'
2494
+ sheet.range(range_four).api.Font.Size = 11
2495
+ sheet.range(range_four).api.HorizontalAlignment = -4108
2496
+ sheet.range(range_four).api.VerticalAlignment = -4108
2497
+ sheet.range(f'B4:U4').color = 0x50d092
2498
+
2499
+ def _format_monthly_header(self, sheet, las_row):
2500
+ """格式化月度表头,完全按照原始格式"""
2501
+ # 第一行:标题(合并A1:S1)
2502
+ range_one = f'A1:S1'
2503
+ sheet.range(range_one).merge()
2504
+ sheet.range(range_one).api.Font.Size = 24
2505
+ sheet.range(range_one).api.Font.Bold = True
2506
+ sheet.range(range_one).api.HorizontalAlignment = -4108
2507
+ sheet.range(range_one).api.VerticalAlignment = -4108
2508
+
2509
+ # 第二行:分类标题
2510
+ range_two_part_1 = f'A2'
2511
+ range_two_part_2 = f'B2:M2'
2512
+ range_two_part_3 = f'N2:S2'
2513
+ sheet.range(range_two_part_2).merge()
2514
+ sheet.range(range_two_part_3).merge()
2515
+
2516
+ sheet.range(f'A2:A3').color = 0x47a100
2517
+
2518
+ sheet.range('B2').value = '店铺的结果和稳定性'
2519
+ sheet.range(range_two_part_2).api.Font.Size = 16
2520
+ sheet.range(range_two_part_2).api.Font.Color = 0xFFFFFF
2521
+ sheet.range(range_two_part_2).api.Font.Bold = True
2522
+ sheet.range(range_two_part_2).api.HorizontalAlignment = -4108
2523
+ sheet.range(range_two_part_2).api.VerticalAlignment = -4108
2524
+ sheet.range(f'B2:M3').color = 0x0000FF
2525
+
2526
+ sheet.range('N2').value = '上新的质量和数量'
2527
+ sheet.range(range_two_part_3).api.Font.Size = 16
2528
+ sheet.range(range_two_part_3).api.Font.Color = 0xFFFFFF
2529
+ sheet.range(range_two_part_3).api.Font.Bold = True
2530
+ sheet.range(range_two_part_3).api.HorizontalAlignment = -4108
2531
+ sheet.range(range_two_part_3).api.VerticalAlignment = -4108
2532
+ sheet.range(f'N2:S3').color = 0x47a100
2533
+
2534
+ # 第三行:列标题
2535
+ range_three = f'A3:S3'
2536
+ sheet.range('A3').value = ['日期', '昨日单量', '对比前日', '昨日销售额', '对比前日', '昨日访客', '对比前天',
2537
+ '备货款A', '对比前日', '新款A', '对比前日', '在售商品', '对比前日', '待上架',
2538
+ '对比前日', '昨日上传', '对比前日', '已售罄', '已下架']
2539
+ sheet.range(range_three).api.Font.Size = 11
2540
+ sheet.range(range_three).api.Font.Color = 0xFFFFFF
2541
+ sheet.range(range_three).api.Font.Bold = True
2542
+ sheet.range(range_three).api.HorizontalAlignment = -4108
2543
+ sheet.range(range_three).api.VerticalAlignment = -4108
2544
+
2545
+ # 第四行:汇总行
2546
+ range_four = f'A4:S4'
2547
+ sheet.range('A4').value = '汇总'
2548
+ sheet.range(range_four).api.Font.Size = 11
2549
+ sheet.range(range_four).api.HorizontalAlignment = -4108
2550
+ sheet.range(range_four).api.VerticalAlignment = -4108
2551
+ sheet.range(f'A4:S4').color = 0x50d092
2552
+
2553
+ def _set_summary_formulas(self, sheet, las_row):
2554
+ """设置汇总公式"""
2555
+ for col in range(2, 22): # B列到U列(跳过A列日期)
2556
+ col_letter = xw.utils.col_name(col)
2557
+ if col_letter not in ['A', 'B', 'C']: # A列是日期,B列是汇总,C列是-
2558
+ sheet.range(f'{col_letter}4').formula = f'=SUM({col_letter}5:{col_letter}{las_row})'
2559
+ # 所有列水平居中和垂直居中
2560
+ sheet.range(f'{col_letter}:{col_letter}').api.HorizontalAlignment = -4108
2561
+ sheet.range(f'{col_letter}:{col_letter}').api.VerticalAlignment = -4108
2562
+
2563
+ def _set_monthly_summary_formulas(self, sheet, las_row):
2564
+ """设置月度汇总公式"""
2565
+ for col in range(2, 20): # B列到S列(对应原始代码的 2 到 20)
2566
+ col_letter = xw.utils.col_name(col)
2567
+ # 所有列水平居中和垂直居中
2568
+ sheet.range(f'{col_letter}:{col_letter}').api.HorizontalAlignment = -4108
2569
+ sheet.range(f'{col_letter}:{col_letter}').api.VerticalAlignment = -4108
2570
+ # 设置汇总公式(原始代码使用固定的36行)
2571
+ sheet.range(f'{col_letter}4').formula = f'=SUM({col_letter}5:{col_letter}36)'
2572
+
2573
+ def _set_borders(self, sheet, range_str):
2574
+ """设置边框"""
2575
+ range_to_border = sheet.range(range_str)
2576
+ # 设置外部边框
2577
+ range_to_border.api.Borders(7).LineStyle = 1 # 上边框
2578
+ range_to_border.api.Borders(8).LineStyle = 1 # 下边框
2579
+ range_to_border.api.Borders(9).LineStyle = 1 # 左边框
2580
+ range_to_border.api.Borders(10).LineStyle = 1 # 右边框
2581
+ # 设置内部边框
2582
+ range_to_border.api.Borders(1).LineStyle = 1 # 内部上边框
2583
+ range_to_border.api.Borders(2).LineStyle = 1 # 内部下边框
2584
+ range_to_border.api.Borders(3).LineStyle = 1 # 内部左边框
2585
+ range_to_border.api.Borders(4).LineStyle = 1 # 内部右边框
11
2586
 
12
2587
  def format_bak_advice(self, excel_path, sheet_name, mode):
13
2588
  app, wb, sheet = open_excel(excel_path, sheet_name)
@@ -25,11 +2600,13 @@ class SheinExcel:
25
2600
  add_formula_for_column(sheet, '本地和采购可售天数', '=IF(H2>0, (F2+G2)/H2,0)')
26
2601
  add_formula_for_column(sheet, '建议采购', '=IF(I2 > J2,0,E2)')
27
2602
 
28
- colorize_by_field(app, wb, sheet, 'SKC')
2603
+ colorize_by_field(sheet, 'SKC')
29
2604
  specify_column_width(sheet, ['商品信息'], 180 / 6)
30
- InsertImageV2(app, wb, sheet, ['SKC图片', 'SKU图片'])
2605
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'])
31
2606
  wb.save()
32
2607
  close_excel(app, wb)
2608
+ if mode == 4:
2609
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
33
2610
 
34
2611
  def write_bak_advice(self, mode_list):
35
2612
  excel_path_list = [
@@ -62,11 +2639,11 @@ class SheinExcel:
62
2639
  new_excel_path_list.append(new_excel_path)
63
2640
  sheet_name = 'Sheet1'
64
2641
 
65
- log(new_excel_path)
66
- if mode in [2]:
67
- excel_data = sort_by_column(excel_data, 4, 1)
68
- write_data(new_excel_path, sheet_name, excel_data)
69
- self.format_bak_advice(new_excel_path, sheet_name, mode)
2642
+ log(new_excel_path)
2643
+ if mode in [2]:
2644
+ excel_data = sort_by_column(excel_data, 4, 1)
2645
+ write_data(new_excel_path, sheet_name, excel_data)
2646
+ self.format_bak_advice(new_excel_path, sheet_name, mode)
70
2647
 
71
2648
  # 是否合并表格数据
72
2649
  if mode in [1, 3]:
@@ -79,3 +2656,732 @@ class SheinExcel:
79
2656
  self.format_bak_advice(new_excel_path, sheet_name, mode)
80
2657
 
81
2658
  return new_excel_path_list
2659
+
2660
+ def write_activity_list(self):
2661
+ cache_file = f'{self.config.auto_dir}/shein/activity_list/activity_list_{TimeUtils.today_date()}.json'
2662
+ dict_activity = read_dict_from_file(cache_file)
2663
+ all_data = []
2664
+ header = []
2665
+ for store_username, excel_data in dict_activity.items():
2666
+ header = excel_data[:1]
2667
+ all_data += excel_data[1:]
2668
+
2669
+ all_data = header + all_data
2670
+
2671
+ excel_path = create_file_path(self.config.excel_activity_list)
2672
+ sheet_name = 'Sheet1'
2673
+ write_data(excel_path, sheet_name, all_data)
2674
+ self.format_activity_list(excel_path, sheet_name)
2675
+
2676
+ def format_activity_list(self, excel_path, sheet_name):
2677
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2678
+ beautify_title(sheet)
2679
+ add_borders(sheet)
2680
+ column_to_left(sheet, ['活动信息'])
2681
+ colorize_by_field(sheet, '店铺名称')
2682
+ autofit_column(sheet, ['店铺名称', '活动信息'])
2683
+ wb.save()
2684
+ close_excel(app, wb)
2685
+
2686
+ def write_jit_data(self):
2687
+ excel_path_1 = create_file_path(self.config.Excel_Order_Type_1)
2688
+ summary_excel_data_1 = []
2689
+
2690
+ cache_file_1 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_1_{TimeUtils.get_period()}.json'
2691
+ dict_1 = read_dict_from_file(cache_file_1)
2692
+ dict_store = read_dict_from_file(f'{self.config.auto_dir}/shein_store_alias.json')
2693
+
2694
+ header = []
2695
+ for store_username, excel_data in dict_1.items():
2696
+ # store_name = dict_store.get(store_username)
2697
+ # sheet_name = store_name
2698
+ # write_data(excel_path_1, sheet_name, excel_data)
2699
+ # self.format_jit(excel_path_1, sheet_name)
2700
+ header = excel_data[0]
2701
+ summary_excel_data_1 += excel_data[1:]
2702
+
2703
+ if len(summary_excel_data_1) > 0:
2704
+ sheet_name = 'Sheet1'
2705
+ write_data(excel_path_1, sheet_name, [header] + summary_excel_data_1)
2706
+ self.format_jit(excel_path_1, sheet_name)
2707
+
2708
+ excel_path_2 = create_file_path(self.config.Excel_Order_Type_2)
2709
+ summary_excel_data_2 = []
2710
+
2711
+ cache_file_2 = f'{self.config.auto_dir}/shein/cache/jit_{TimeUtils.today_date()}_2_{TimeUtils.get_period()}.json'
2712
+ dict_2 = read_dict_from_file(cache_file_2)
2713
+
2714
+ header = []
2715
+ for store_username, excel_data in dict_2.items():
2716
+ # store_name = dict_store.get(store_username)
2717
+ # sheet_name = store_name
2718
+ # write_data(excel_path_2, sheet_name, excel_data)
2719
+ # self.format_jit(excel_path_2, sheet_name)
2720
+ header = excel_data[0]
2721
+ summary_excel_data_2 += excel_data[1:]
2722
+
2723
+ if len(summary_excel_data_2) > 0:
2724
+ sheet_name = 'Sheet1'
2725
+ write_data(excel_path_2, sheet_name, [header] + summary_excel_data_2)
2726
+ self.format_jit(excel_path_2, sheet_name)
2727
+
2728
+ def format_jit(self, excel_path, sheet_name):
2729
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2730
+ beautify_title(sheet)
2731
+ add_borders(sheet)
2732
+ colorize_by_field(sheet, 'SKC')
2733
+ column_to_left(sheet, ["商品信息", "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
2734
+ autofit_column(sheet,
2735
+ ['店铺名称', '商品信息', "近7天SKU销量/SKC销量/SKC曝光", "SKC点击率/SKC转化率", "自主参与活动"])
2736
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'])
2737
+ wb.save()
2738
+ close_excel(app, wb)
2739
+ WxWorkBot('b30aaa8d-1a1f-4378-841a-8b0f8295f2d9').send_file(excel_path)
2740
+
2741
+ def write_week_report(self):
2742
+ excel_path = create_file_path(self.config.excel_week_sales_report)
2743
+ log(excel_path)
2744
+
2745
+ cache_file = f'{self.config.auto_dir}/shein/cache/week_sales_{TimeUtils.today_date()}.json'
2746
+ dict = read_dict_from_file(cache_file)
2747
+
2748
+ summary_excel_data = []
2749
+ header = []
2750
+ for store_name, excel_data in dict.items():
2751
+ # sheet_name = store_name
2752
+ # write_data(excel_path, sheet_name, excel_data)
2753
+ # self.format_week_report(excel_path, sheet_name)
2754
+ header = excel_data[0]
2755
+ summary_excel_data += excel_data[1:]
2756
+ summary_excel_data = [header] + summary_excel_data
2757
+ sheet_name = 'Sheet1'
2758
+ write_data(excel_path, sheet_name, summary_excel_data)
2759
+ self.format_week_report(excel_path, sheet_name)
2760
+
2761
+ def format_week_report(self, excel_path, sheet_name):
2762
+ app, wb, sheet = open_excel(excel_path, sheet_name)
2763
+ beautify_title(sheet)
2764
+ column_to_left(sheet, ['商品信息'])
2765
+ format_to_money(sheet, ['申报价', '成本价', '毛利润', '利润'])
2766
+ format_to_percent(sheet, ['支付率', '点击率', '毛利率'])
2767
+ self.dealFormula(sheet) # 有空再封装优化
2768
+ colorize_by_field(sheet, 'SPU')
2769
+ autofit_column(sheet, ['商品信息', '店铺名称', 'SKC点击率/SKC转化率', '自主参与活动'])
2770
+ column_to_left(sheet, ['店铺名称', 'SKC点击率/SKC转化率', '自主参与活动', '近7天SKU销量/SKC销量/SKC曝光'])
2771
+ specify_column_width(sheet, ['商品标题'], 150 / 6)
2772
+ add_borders(sheet)
2773
+ InsertImageV2(sheet, ['SKC图片', 'SKU图片'], 'shein', 120, None, None, True)
2774
+ wb.save()
2775
+ close_excel(app, wb)
2776
+
2777
+ # 处理公式计算
2778
+ def dealFormula(self, sheet):
2779
+ # 增加列 周销增量 月销增量
2780
+ col_week_increment = find_column_by_data(sheet, 1, '周销增量')
2781
+ if col_week_increment is None:
2782
+ col_week_increment = find_column_by_data(sheet, 1, '远30天销量')
2783
+ log(f'{col_week_increment}:{col_week_increment}')
2784
+ sheet.range(f'{col_week_increment}:{col_week_increment}').insert('right')
2785
+ sheet.range(f'{col_week_increment}1').value = '周销增量'
2786
+ log('已增加列 周销增量')
2787
+
2788
+ col_month_increment = find_column_by_data(sheet, 1, '月销增量')
2789
+ if col_month_increment is None:
2790
+ col_month_increment = find_column_by_data(sheet, 1, '总销量')
2791
+ log(f'{col_month_increment}:{col_month_increment}')
2792
+ sheet.range(f'{col_month_increment}:{col_month_increment}').insert('right')
2793
+ sheet.range(f'{col_month_increment}1').value = '月销增量'
2794
+ log('已增加列 月销增量')
2795
+
2796
+ col_month_profit = find_column_by_data(sheet, 1, '近30天利润')
2797
+ if col_month_profit is None:
2798
+ col_month_profit = find_column_by_data(sheet, 1, '总利润')
2799
+ sheet.range(f'{col_month_profit}:{col_month_profit}').insert('right')
2800
+ log((f'{col_month_profit}:{col_month_profit}'))
2801
+ sheet.range(f'{col_month_profit}1').value = '近30天利润'
2802
+ log('已增加列 近30天利润')
2803
+
2804
+ col_week_profit = find_column_by_data(sheet, 1, '近7天利润')
2805
+ if col_week_profit is None:
2806
+ col_week_profit = find_column_by_data(sheet, 1, '近30天利润')
2807
+ sheet.range(f'{col_week_profit}:{col_week_profit}').insert('right')
2808
+ log((f'{col_week_profit}:{col_week_profit}'))
2809
+ sheet.range(f'{col_week_profit}1').value = '近7天利润'
2810
+ log('已增加列 近7天利润')
2811
+
2812
+ # return
2813
+
2814
+ # 查找 申报价,成本价,毛利润,毛利润率 所在列
2815
+ col_verify_price = find_column_by_data(sheet, 1, '申报价')
2816
+ col_cost_price = find_column_by_data(sheet, 1, '成本价')
2817
+ col_gross_profit = find_column_by_data(sheet, 1, '毛利润')
2818
+ col_gross_margin = find_column_by_data(sheet, 1, '毛利率')
2819
+
2820
+ col_week_1 = find_column_by_data(sheet, 1, '近7天销量')
2821
+ col_week_2 = find_column_by_data(sheet, 1, '远7天销量')
2822
+ col_month_1 = find_column_by_data(sheet, 1, '近30天销量')
2823
+ col_month_2 = find_column_by_data(sheet, 1, '远30天销量')
2824
+
2825
+ # 遍历可用行
2826
+ used_range_row = sheet.range('A1').expand('down')
2827
+ for i, cell in enumerate(used_range_row):
2828
+ row = i + 1
2829
+ if row < 2:
2830
+ continue
2831
+ rangeA = f'{col_verify_price}{row}'
2832
+ rangeB = f'{col_cost_price}{row}'
2833
+
2834
+ rangeC = f'{col_week_increment}{row}'
2835
+ rangeD = f'{col_month_increment}{row}'
2836
+
2837
+ # rangeE = f'{col_total_profit}{row}'
2838
+ rangeF = f'{col_month_profit}{row}'
2839
+ rangeG = f'{col_week_profit}{row}'
2840
+
2841
+ # 设置毛利润和毛利润率列公式与格式
2842
+ sheet.range(f'{col_gross_profit}{row}').formula = f'=IF(ISNUMBER({rangeB}),{rangeA}-{rangeB},"")'
2843
+ sheet.range(f'{col_gross_profit}{row}').number_format = '0.00'
2844
+ sheet.range(f'{col_gross_margin}{row}').formula = f'=IF(ISNUMBER({rangeB}),({rangeA}-{rangeB})/{rangeA},"")'
2845
+ sheet.range(f'{col_gross_margin}{row}').number_format = '0.00%'
2846
+
2847
+ sheet.range(rangeC).formula = f'={col_week_1}{row}-{col_week_2}{row}'
2848
+ sheet.range(rangeC).number_format = '0'
2849
+ sheet.range(rangeD).formula = f'={col_month_1}{row}-{col_month_2}{row}'
2850
+ sheet.range(rangeD).number_format = '0'
2851
+
2852
+ # sheet.range(rangeE).formula = f'=IF(ISNUMBER({rangeB}),{col_total}{row}*{col_gross_profit}{row},"")'
2853
+ # sheet.range(rangeE).number_format = '0.00'
2854
+ sheet.range(rangeF).formula = f'=IF(ISNUMBER({rangeB}),{col_month_1}{row}*{col_gross_profit}{row},"")'
2855
+ sheet.range(rangeF).number_format = '0.00'
2856
+ sheet.range(rangeG).formula = f'=IF(ISNUMBER({rangeB}),{col_week_1}{row}*{col_gross_profit}{row},"")'
2857
+ sheet.range(rangeG).number_format = '0.00'
2858
+
2859
+ def write_check_order(self, erp, start_date, end_date):
2860
+ header = ['店铺账号', '店铺别名', '店长', '报账单号', '货号', 'SKC', '平台SKU', '商家SKU', '属性集', '商品数量', '账单类型', '收支类型', '状态', '币种', '金额', 'ERP成本',
2861
+ '成本总额', '业务单号', '费用类型', '备注', '来源单号', '账单创建时间', '台账添加时间', '报账时间', '预计结算日期', '实际结算日期']
2862
+ excel_data = [header]
2863
+
2864
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
2865
+
2866
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
2867
+ dict = read_dict_from_file(cache_file)
2868
+ for store_username, data_list in dict.items():
2869
+ for item in data_list:
2870
+ store_name = dict_store.get(store_username)
2871
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
2872
+
2873
+ row_item = []
2874
+ row_item.append(store_username)
2875
+ row_item.append(store_name)
2876
+ row_item.append(store_manager)
2877
+ row_item.append(item['reportOrderNo'])
2878
+ row_item.append(item['goodsSn'])
2879
+ row_item.append(item['skcName'])
2880
+ row_item.append(item['skuCode'])
2881
+ row_item.append(item['skuSn'])
2882
+ row_item.append(item['suffix'])
2883
+ row_item.append(item['goodsCount'])
2884
+ row_item.append(item['secondOrderTypeName'])
2885
+ row_item.append(item['inAndOutName'])
2886
+ row_item.append(item['settlementStatusName'])
2887
+ row_item.append(item['settleCurrencyCode'])
2888
+ row_item.append(item['income'])
2889
+ row_item.append(self.bridge.get_sku_cost(item['skuSn'], erp))
2890
+ row_item.append('')
2891
+ row_item.append(item['bzOrderNo'])
2892
+ row_item.append(item['expenseTypeName'])
2893
+ row_item.append(item['remark'])
2894
+ row_item.append(item['sourceNo'])
2895
+ row_item.append(item['addTime'])
2896
+ row_item.append(item['businessCompletedTime'])
2897
+ row_item.append(item['reportTime'])
2898
+ row_item.append(item['estimatePayTime'])
2899
+ row_item.append(item['completedPayTime'])
2900
+
2901
+ excel_data.append(row_item)
2902
+
2903
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
2904
+ write_dict_to_file(cache_file_excel, excel_data)
2905
+
2906
+ sheet_name = '收支明细'
2907
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
2908
+ (sheet_name, 'write', excel_data, ['R']),
2909
+ (sheet_name, 'format', self.format_check_order)
2910
+ ])
2911
+
2912
+ header = ['店铺账号', '店铺别名', '店长', '出库金额', '出库成本', '备货作业费', '代收服务费', '订单履约服务费', '订单退货', '退货处理费', '退货单履约服务费', '利润']
2913
+ excel_data = [header]
2914
+ cache_file = f'{self.config.auto_dir}/shein/cache/check_order_{start_date}_{end_date}.json'
2915
+ dict = read_dict_from_file(cache_file)
2916
+ for store_username, data_list in dict.items():
2917
+ store_name = dict_store.get(store_username)
2918
+ store_manager = self.config.shein_store_manager.get(str(store_username).lower())
2919
+ row_item = []
2920
+ row_item.append(store_username)
2921
+ row_item.append(store_name)
2922
+ row_item.append(store_manager)
2923
+ row_item.append('')
2924
+ row_item.append('')
2925
+ row_item.append('')
2926
+ row_item.append('')
2927
+ row_item.append('')
2928
+ row_item.append('')
2929
+ row_item.append('')
2930
+ row_item.append('')
2931
+ row_item.append('')
2932
+ excel_data.append(row_item)
2933
+
2934
+ sheet_name = '总表'
2935
+ batch_excel_operations(self.config.excel_shein_finance_month_report_pop, [
2936
+ (sheet_name, 'write', excel_data),
2937
+ (sheet_name, 'format', self.format_check_order),
2938
+ ('Sheet1', 'delete'),
2939
+ (sheet_name, 'move', 1),
2940
+ ])
2941
+
2942
+ def merge_finance_details_to_summary(self, source_dir, start_date, end_date, output_dir=None):
2943
+ """
2944
+ 将多个店铺的财务收支明细Excel文件合并到一个汇总Excel中
2945
+
2946
+ Args:
2947
+ source_dir: 源文件目录路径
2948
+ start_date: 开始日期,格式: YYYY-MM-DD
2949
+ end_date: 结束日期,格式: YYYY-MM-DD
2950
+ output_dir: 输出目录,默认为None则使用source_dir
2951
+
2952
+ Returns:
2953
+ 汇总Excel文件路径
2954
+ """
2955
+ import os
2956
+ import glob
2957
+
2958
+ # 确定输出目录
2959
+ if output_dir is None:
2960
+ output_dir = source_dir
2961
+ os.makedirs(output_dir, exist_ok=True)
2962
+
2963
+ # 提取月份(从start_date中提取)
2964
+ from datetime import datetime
2965
+ start_dt = datetime.strptime(start_date, '%Y-%m-%d')
2966
+ month_str = f"{start_dt.month}月"
2967
+
2968
+ # 生成输出文件名
2969
+ output_filename = f'希音财务月报POP-{month_str}.xlsx'
2970
+ output_path = os.path.join(output_dir, output_filename)
2971
+
2972
+ log(f'开始合并财务收支明细: {source_dir}')
2973
+
2974
+ # 查找所有匹配的Excel文件
2975
+ pattern = os.path.join(source_dir, f'finance_details_*_{start_date}_{end_date}.xlsx')
2976
+ excel_files = glob.glob(pattern)
2977
+
2978
+ if len(excel_files) == 0:
2979
+ log(f'未找到匹配的文件: {pattern}')
2980
+ raise Exception(f'未找到匹配的财务收支明细文件')
2981
+
2982
+ log(f'找到 {len(excel_files)} 个Excel文件待合并')
2983
+
2984
+ # 读取所有Excel文件并合并数据
2985
+ all_detail_data = []
2986
+ store_list = [] # 存储店铺账号列表
2987
+ header = None
2988
+
2989
+ for idx, excel_file in enumerate(excel_files):
2990
+ log(f'读取文件 {idx + 1}/{len(excel_files)}: {os.path.basename(excel_file)}')
2991
+
2992
+ # 从文件名中提取店铺账号
2993
+ filename = os.path.basename(excel_file)
2994
+ # 格式: finance_details_{store_username}_{start_date}_{end_date}.xlsx
2995
+ parts = filename.replace('.xlsx', '').split('_')
2996
+ if len(parts) >= 5:
2997
+ store_username = parts[2] # finance_details_{store_username}_...
2998
+ store_list.append(store_username)
2999
+ else:
3000
+ log(f'警告:无法从文件名提取店铺账号: {filename}')
3001
+ store_username = 'unknown'
3002
+ store_list.append(store_username)
3003
+
3004
+ # 读取Excel文件
3005
+ try:
3006
+ df = pd.read_excel(excel_file, sheet_name=0)
3007
+
3008
+ if idx == 0:
3009
+ # 第一个文件,保存表头
3010
+ header = df.columns.tolist()
3011
+ log(f'表头列数: {len(header)}')
3012
+
3013
+ # 读取数据(跳过表头)
3014
+ data_rows = df.values.tolist()
3015
+ log(f'读取到 {len(data_rows)} 行数据')
3016
+
3017
+ # 添加到总数据中
3018
+ all_detail_data.extend(data_rows)
3019
+
3020
+ except Exception as e:
3021
+ log(f'读取文件失败: {excel_file}, 错误: {str(e)}')
3022
+ continue
3023
+
3024
+ log(f'合并完成,总共 {len(all_detail_data)} 行数据')
3025
+
3026
+ dict_store_manager_shein = self.config.shein_store_manager
3027
+ dict_store_name = read_dict_from_file(self.config.shein_store_alias)
3028
+
3029
+ # 在"金额"列后面添加"ERP成本"和"成本总额"两列
3030
+ if header:
3031
+ # 查找"金额"列和"商家SKU"列的位置
3032
+ amount_col_idx = None
3033
+ sku_col_idx = None
3034
+ business_no_col_idx = None # 业务单号列索引
3035
+
3036
+ for i, col_name in enumerate(header):
3037
+ if '金额' in str(col_name):
3038
+ amount_col_idx = i
3039
+ if '商家SKU' in str(col_name) or 'SKU' in str(col_name):
3040
+ sku_col_idx = i
3041
+ if '店铺名称' in str(col_name):
3042
+ store_name_idx = i
3043
+ if '店长' in str(col_name):
3044
+ store_manager_idx = i
3045
+ if '业务单号' in str(col_name):
3046
+ business_no_col_idx = i
3047
+
3048
+ if amount_col_idx is not None:
3049
+ # 在"金额"列后面插入两列
3050
+ new_header = header[:amount_col_idx + 1] + ['ERP成本', '成本总额'] + header[amount_col_idx + 1:]
3051
+ log(f'在第{amount_col_idx + 1}列(金额)后面插入"ERP成本"和"成本总额"两列')
3052
+
3053
+ # 业务单号列在插入新列后的索引需要调整
3054
+ if business_no_col_idx is not None and business_no_col_idx > amount_col_idx:
3055
+ business_no_col_idx_adjusted = business_no_col_idx + 2 # 因为插入了2列
3056
+ else:
3057
+ business_no_col_idx_adjusted = business_no_col_idx
3058
+
3059
+ # 处理数据行:在相应位置插入ERP成本和成本总额
3060
+ new_data = []
3061
+ for row_idx, row in enumerate(all_detail_data):
3062
+ # 转换为list(如果是tuple)
3063
+ row_list = list(row)
3064
+
3065
+ store_username = row_list[0]
3066
+ store_name = dict_store_name.get(store_username)
3067
+ store_manager = dict_store_manager_shein.get(str(store_username).lower(), '-')
3068
+ row_list[1] = store_name
3069
+ row_list[2] = store_manager
3070
+
3071
+ # 获取商家SKU
3072
+ sku = None
3073
+ if sku_col_idx is not None and sku_col_idx < len(row_list):
3074
+ sku = row_list[sku_col_idx]
3075
+
3076
+ # 获取ERP成本
3077
+ erp_cost = ''
3078
+ if sku and self.bridge:
3079
+ try:
3080
+ erp_cost = self.bridge.get_sku_cost(str(sku), self.config.erp_source)
3081
+ except Exception as e:
3082
+ log(f'获取SKU成本失败: {sku}, 错误: {str(e)}')
3083
+
3084
+ # 在"金额"列后面插入两列数据
3085
+ new_row = row_list[:amount_col_idx + 1] + [erp_cost, ''] + row_list[amount_col_idx + 1:]
3086
+ new_data.append(new_row)
3087
+
3088
+ # 每1000行输出一次进度
3089
+ if (row_idx + 1) % 1000 == 0:
3090
+ log(f'处理进度: {row_idx + 1}/{len(all_detail_data)}')
3091
+
3092
+ # 更新表头和数据
3093
+ header = new_header
3094
+ all_detail_data = new_data
3095
+ log(f'ERP成本数据处理完成,新表头列数: {len(header)}')
3096
+ if business_no_col_idx_adjusted is not None:
3097
+ log(f'业务单号列已转换为字符串格式,列索引: {business_no_col_idx_adjusted}')
3098
+ else:
3099
+ log('警告:未找到"金额"列,无法添加ERP成本和成本总额列')
3100
+
3101
+ # 准备汇总表数据
3102
+ summary_header = ['店铺账号', '店铺别名', '店长', '出库金额', '出库成本', '备货作业费',
3103
+ '代收服务费', '订单履约服务费', '订单退货', '退货处理费', '退货单履约服务费', '利润']
3104
+ summary_data = [summary_header]
3105
+
3106
+ # 为每个店铺创建一行(其他字段留空,由公式计算)
3107
+ for store_username in store_list:
3108
+ store_name = dict_store_name.get(store_username)
3109
+ store_manager = dict_store_manager_shein.get(str(store_username).lower(), '-')
3110
+ row = [store_username, store_name, store_manager, '', '', '', '', '', '', '', '', '']
3111
+ summary_data.append(row)
3112
+
3113
+ # 写入Excel文件
3114
+ log(f'开始写入汇总Excel: {output_path}')
3115
+
3116
+ # 查找需要格式化为文本的列
3117
+ text_format_columns = []
3118
+ str_keywords = ['业务单号']
3119
+
3120
+ def col_idx_to_letter(idx):
3121
+ """将列索引转换为Excel列字母 (0->A, 1->B, ..., 25->Z, 26->AA, ...)"""
3122
+ result = ''
3123
+ idx += 1 # Excel列从1开始
3124
+ while idx > 0:
3125
+ idx -= 1
3126
+ result = chr(65 + idx % 26) + result
3127
+ idx //= 26
3128
+ return result
3129
+
3130
+ for col_idx, col_name in enumerate(header):
3131
+ col_name_str = str(col_name)
3132
+ # 检查列名是否包含需要保持为文本的关键词
3133
+ if any(keyword in col_name_str for keyword in str_keywords):
3134
+ col_letter = col_idx_to_letter(col_idx)
3135
+ text_format_columns.append(col_letter)
3136
+ log(f'列"{col_name}"(第{col_idx}列,Excel列{col_letter})将格式化为文本')
3137
+
3138
+ log(f'共{len(text_format_columns)}列需要格式化为文本: {text_format_columns}')
3139
+
3140
+ # 使用batch_excel_operations批量写入和格式化
3141
+ operations = [
3142
+ ('财务收支明细', 'write', [header] + all_detail_data, text_format_columns),
3143
+ ('财务收支明细', 'format', self.format_check_order),
3144
+ ('总表', 'write', summary_data),
3145
+ ('总表', 'format', self.format_check_order),
3146
+ ('Sheet1', 'delete'),
3147
+ ('总表', 'move', 1),
3148
+ ]
3149
+
3150
+ batch_excel_operations(output_path, operations)
3151
+
3152
+ log(f'合并完成,文件已保存: {output_path}')
3153
+ return output_path
3154
+
3155
+ def format_check_order(self, sheet):
3156
+ if sheet.name == '收支明细' or sheet.name == '财务收支明细':
3157
+ beautify_title(sheet)
3158
+ add_borders(sheet)
3159
+ format_to_datetime(sheet, ['时间'])
3160
+ format_to_date(sheet, ['日期'])
3161
+ format_to_money(sheet, ['金额', '成本', 'ERP成本', '成本总额'])
3162
+ column_to_right(sheet, ['金额', '成本', 'ERP成本', '成本总额'])
3163
+ column_to_left(sheet, ['货号', '商家SKU'])
3164
+
3165
+ # 将业务单号列格式化为文本格式
3166
+ try:
3167
+ from .fun_excel import find_column_by_data
3168
+ business_no_col = find_column_by_data(sheet, 1, '业务单号')
3169
+ if business_no_col:
3170
+ # 设置整列为文本格式
3171
+ last_row = sheet.range('A' + str(sheet.cells.last_cell.row)).end('up').row
3172
+ sheet.range(f'{business_no_col}2:{business_no_col}{last_row}').number_format = '@'
3173
+ log(f'业务单号列({business_no_col})已设置为文本格式')
3174
+ except Exception as e:
3175
+ log(f'设置业务单号列格式失败: {str(e)}')
3176
+
3177
+ # 为成本总额列添加公式
3178
+ # 成本总额 = 数量 * ERP成本
3179
+ try:
3180
+ # 查找"数量"列和"ERP成本"列的位置
3181
+ from .fun_excel import find_column_by_data
3182
+ quantity_col = find_column_by_data(sheet, 1, '商品数量')
3183
+ erp_cost_col = find_column_by_data(sheet, 1, 'ERP成本')
3184
+
3185
+ log("数量,ERP成本", quantity_col, erp_cost_col)
3186
+ if quantity_col and erp_cost_col:
3187
+ # 添加公式:成本总额 = 数量 * ERP成本
3188
+ add_formula_for_column(sheet, '成本总额', f'=IF(ISNUMBER({erp_cost_col}2),{quantity_col}2*{erp_cost_col}2,"-")')
3189
+ log('成本总额公式已添加')
3190
+ except Exception as e:
3191
+ log(f'添加成本总额公式失败: {str(e)}')
3192
+
3193
+ if sheet.name == '总表':
3194
+ beautify_title(sheet)
3195
+ add_borders(sheet)
3196
+ format_to_money(sheet, ['金额', '成本', '费', '订单退货', '利润'])
3197
+ column_to_right(sheet, ['金额', '成本', '费', '订单退货', '利润'])
3198
+
3199
+ # 使用财务收支明细sheet的引用
3200
+ detail_sheet = '财务收支明细' if '财务收支明细' in [s.name for s in sheet.book.sheets] else '收支明细'
3201
+
3202
+ # 查找财务收支明细sheet中各列的位置
3203
+ from .fun_excel import find_column_by_data
3204
+ detail_ws = None
3205
+ for ws in sheet.book.sheets:
3206
+ if ws.name == detail_sheet:
3207
+ detail_ws = ws
3208
+ break
3209
+
3210
+ if detail_ws:
3211
+ # 查找关键列的位置
3212
+ amount_col = find_column_by_data(detail_ws, 1, '金额') # 金额列
3213
+ cost_total_col = find_column_by_data(detail_ws, 1, '成本总额') # 成本总额列
3214
+ store_col = find_column_by_data(detail_ws, 1, '店铺账号') # 店铺账号列
3215
+ type_col = find_column_by_data(detail_ws, 1, '收支类型') # 收支类型列
3216
+ bill_type_col = find_column_by_data(detail_ws, 1, '账单类型') # 账单类型列
3217
+
3218
+ log(f'找到列位置: 金额={amount_col}, 成本总额={cost_total_col}, 店铺账号={store_col}, 收支类型={type_col}, 账单类型={bill_type_col}')
3219
+
3220
+ # 使用找到的列位置生成公式
3221
+ if amount_col and store_col and type_col:
3222
+ 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},"收入")')
3223
+
3224
+ if cost_total_col and store_col and type_col:
3225
+ # 使用成本总额列
3226
+ 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},"收入")')
3227
+
3228
+ if amount_col and store_col and type_col and bill_type_col:
3229
+ 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},"备货作业费")')
3230
+ 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},"代收服务费")')
3231
+ 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},"订单履约服务费")')
3232
+ 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},"订单退货")')
3233
+ 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},"退货处理费")')
3234
+ 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},"退货单履约服务费")')
3235
+
3236
+ add_formula_for_column(sheet, '利润', '=D2-E2-F2-G2-H2-I2-J2-K2')
3237
+
3238
+ def write_vssv_order_list(self):
3239
+ """
3240
+ 写入VSSV增值服务订单列表到Excel
3241
+ """
3242
+ # 获取上个月的时间范围
3243
+ first_day, last_day = TimeUtils.get_last_month_range()
3244
+ last_month = TimeUtils.get_last_month()
3245
+
3246
+ # 读取店铺别名映射
3247
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
3248
+
3249
+ # 准备Excel数据
3250
+ header = ['店铺账号', '店铺名称', '增值服务订单号', '增值服务单号', '采购订单号',
3251
+ '平台SKC', '商家SKC', 'SKC数量', '扣款单号', '订单状态', '实际总金额',
3252
+ '增值服务项', '创建时间', '完成时间']
3253
+ excel_data = [header]
3254
+
3255
+ # 遍历vssv_order目录下的所有店铺数据
3256
+ src_directory = f'{self.config.auto_dir}/shein/vssv_order'
3257
+
3258
+ if not os.path.exists(src_directory):
3259
+ log(f'VSSV订单目录不存在: {src_directory}')
3260
+ return
3261
+
3262
+ for entry in os.listdir(src_directory):
3263
+ # 检查是否为匹配的缓存文件
3264
+ if entry.startswith(f"vssv_order_list_") and entry.endswith(f"_{first_day}_{last_day}.json"):
3265
+ file_path = os.path.join(src_directory, entry)
3266
+
3267
+ # 从文件名中提取店铺账号
3268
+ # 格式: vssv_order_list_{store_username}_{first_day}_{last_day}.json
3269
+ parts = entry.replace('.json', '').split('_')
3270
+ if len(parts) >= 5:
3271
+ # vssv_order_list_{store_username}_{first_day}_{last_day}
3272
+ # parts[0]='vssv', parts[1]='order', parts[2]='list', parts[3]=store_username
3273
+ store_username = parts[3]
3274
+ else:
3275
+ log(f'无法解析店铺账号: {entry}')
3276
+ continue
3277
+
3278
+ # 获取店铺名称
3279
+ store_name = dict_store.get(store_username, store_username)
3280
+
3281
+ # 读取订单数据
3282
+ order_list = read_dict_from_file(file_path)
3283
+ log(f'读取店铺 {store_name}({store_username}) 的VSSV订单: {len(order_list)}条')
3284
+
3285
+ # 处理每条订单数据
3286
+ for order in order_list:
3287
+ # 基础订单信息
3288
+ order_no = order.get('orderNo', '-')
3289
+ sub_order_no = order.get('subOrderNo', '-')
3290
+ purchase_no = order.get('purchaseNo', '-')
3291
+ skc_img_path = order.get('skcImgPath', '')
3292
+ skc = order.get('skc', '-')
3293
+ supplier_product_number = order.get('supplierProductNumber', '-')
3294
+ skc_num = order.get('skcNum', 0)
3295
+ order_state_name = order.get('orderStateName', '-')
3296
+ actual_total_amount = order.get('actualTotalAmount', 0)
3297
+
3298
+ # 提取扣款单号(从vendorRepairList数组中)
3299
+ vendor_repair_no = '-'
3300
+ vendor_repair_list = order.get('vendorRepairList', [])
3301
+ if vendor_repair_list and len(vendor_repair_list) > 0:
3302
+ vendor_repair_no = vendor_repair_list[0].get('vendorRepairNo', '-')
3303
+
3304
+ # 提取创建时间和完成时间(从orderChangeLogVo中)
3305
+ create_time = '-'
3306
+ finish_time = '-'
3307
+ order_change_log = order.get('orderChangeLogVo', [])
3308
+ for log_item in order_change_log:
3309
+ if log_item.get('operateType') == 12: # 创建时间
3310
+ create_time = log_item.get('operateTime', '-')
3311
+ elif log_item.get('operateType') == 4: # 增值订单完成时间
3312
+ finish_time = log_item.get('operateTime', '-')
3313
+
3314
+ # 获取增值服务项列表并合并成一个字符串
3315
+ service_items = order.get('subOrderServiceItemVoList', [])
3316
+ service_items_text = '-'
3317
+
3318
+ if service_items:
3319
+ # 将所有服务项合并成一个字符串,每个服务项一行
3320
+ service_lines = []
3321
+ for service_item in service_items:
3322
+ service_name = service_item.get('serviceItemName', '-')
3323
+ settlement_qty = service_item.get('settlementQuantity', 0)
3324
+ item_amount = service_item.get('itemTotalAmount', 0)
3325
+ price = service_item.get('price', 0)
3326
+ # 格式:服务项名称 | 数量 | 金额
3327
+ service_line = f"{service_name}: {settlement_qty}x{price}=¥{item_amount}"
3328
+ service_lines.append(service_line)
3329
+ service_items_text = '\n'.join(service_lines)
3330
+
3331
+ # 添加一行数据
3332
+ row_item = []
3333
+ row_item.append(store_username) # 店铺账号
3334
+ row_item.append(store_name) # 店铺名称
3335
+ row_item.append(order_no) # 增值服务订单号
3336
+ row_item.append(sub_order_no) # 增值服务单号
3337
+ row_item.append(purchase_no) # 采购订单号
3338
+ # row_item.append(skc_img_path) # SKC图片
3339
+ row_item.append(skc) # 平台SKC
3340
+ row_item.append(supplier_product_number) # 商家SKC
3341
+ row_item.append(skc_num) # SKC数量
3342
+ row_item.append(vendor_repair_no) # 扣款单号
3343
+ row_item.append(order_state_name) # 订单状态
3344
+ row_item.append(actual_total_amount) # 实际总金额
3345
+ row_item.append(service_items_text) # 增值服务项(合并)
3346
+ row_item.append(create_time) # 创建时间
3347
+ row_item.append(finish_time) # 完成时间
3348
+ excel_data.append(row_item)
3349
+
3350
+ log(f'共收集到 {len(excel_data) - 1} 条VSSV订单数据')
3351
+
3352
+ # 如果没有数据,只有表头,则不生成Excel
3353
+ if len(excel_data) <= 1:
3354
+ log('没有VSSV订单数据,跳过Excel生成')
3355
+ return
3356
+
3357
+ # 写入Excel
3358
+ excel_path = self.config.excel_path
3359
+ sheet_name = f'{last_month}月增值服务列表'
3360
+
3361
+ batch_excel_operations(excel_path, [
3362
+ (sheet_name, 'write', excel_data, ['C', 'D', 'E', 'I']), # 订单号、单号、采购单号、扣款单号格式化为文本
3363
+ (sheet_name, 'format', self.format_vssv_order_list),
3364
+ ('Sheet1', 'delete')
3365
+ ])
3366
+
3367
+ log(f'VSSV订单列表已写入: {excel_path}')
3368
+
3369
+ def format_vssv_order_list(self, sheet):
3370
+ """
3371
+ 格式化VSSV订单列表Excel
3372
+ """
3373
+ beautify_title(sheet)
3374
+ add_borders(sheet)
3375
+ format_to_money(sheet, ['金额', '总金额'])
3376
+ column_to_right(sheet, ['金额', '数量', '总金额'])
3377
+ format_to_datetime(sheet, ['时间'])
3378
+ column_to_left(sheet, ['店铺账号', '订单号', '单号', 'SKC', '增值服务项'])
3379
+ wrap_column(sheet, ['增值服务项']) # 增值服务项列自动换行
3380
+ autofit_column(sheet, ['店铺名称', '订单状态'])
3381
+ specify_column_width(sheet, ['增值服务订单号', '增值服务单号', '采购订单号', '扣款单号'], 160 / 6)
3382
+ specify_column_width(sheet, ['增值服务项'], 280 / 6) # 服务项列设置较宽
3383
+
3384
+ # 插入SKC图片
3385
+ # InsertImageV2(sheet, ['SKC图片'], 'shein', 90)
3386
+
3387
+ sheet.autofit()