export_all_member_remaining_rights.py
13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
导出所有用户的剩余权益数据Excel文件
包含字段:客户档案号、姓名、手机号、注册时间、归属门店、来源、品项名称、单价、总购买次数、已消费次数、已退卡次数、已扣除次数、剩余次数、剩余价值等
"""
import pymysql
import pandas as pd
from datetime import datetime
import os
# 数据库配置
DB_CONFIG = {
'host': 'rm-2vccze142rc9a8f58bo.mysql.cn-chengdu.rds.aliyuncs.com',
'port': 3306,
'user': 'lvqiansql',
'password': 'LvQ1@n!20251211',
'database': 'lqerp_dev',
'charset': 'utf8mb4',
'connect_timeout': 60,
'read_timeout': 300,
'write_timeout': 300
}
def query_data():
"""查询所有用户的剩余权益数据(分批查询优化)"""
connection = None
try:
connection = pymysql.connect(**DB_CONFIG)
cursor = connection.cursor()
print("步骤1/4: 查询基础开单品项数据...")
# 先查询基础数据
base_sql = """
SELECT
pxmx.F_Id AS 开单品项明细ID,
pxmx.F_MemberId AS 会员ID,
pxmx.glkdbh AS 开单编号,
pxmx.px AS 品项ID,
pxmx.pxmc AS 品项名称,
pxmx.F_ItemCategory AS 品项分类,
pxmx.F_BeautyType AS 科美类型,
pxmx.F_PerformanceType AS 业绩类型,
pxmx.F_SourceType AS 来源类型,
pxmx.pxjg AS 单价,
pxmx.F_ProjectNumber AS 总购买次数,
pxmx.F_TotalPrice AS 总金额,
pxmx.F_ActualPrice AS 实付金额,
pxmx.yjsj AS 业绩时间,
pxmx.F_Remark AS 备注
FROM lq_kd_pxmx pxmx
WHERE pxmx.F_IsEffective = 1
"""
base_df = pd.read_sql(base_sql, connection)
print(f" 查询到 {len(base_df)} 条开单品项记录")
if base_df.empty:
return pd.DataFrame()
print("步骤2/4: 查询客户信息...")
# 查询客户信息
member_ids = base_df['会员ID'].unique().tolist()
if not member_ids:
return pd.DataFrame()
# 查询客户信息(包括已失效的客户,因为可能还有剩余权益)
member_sql = f"""
SELECT
kh.F_Id AS 会员ID,
kh.F_Id AS 客户编号,
COALESCE(kh.dah, '') AS 客户档案号,
COALESCE(kh.khmc, '') AS 客户姓名,
COALESCE(kh.sjh, '') AS 手机号,
kh.zcsj AS 注册时间,
kh.gsmd AS 门店ID,
COALESCE(md.dm, '') AS 归属门店
FROM lq_khxx kh
LEFT JOIN lq_mdxx md ON kh.gsmd = md.F_Id
WHERE kh.F_Id IN ({','.join(['%s'] * len(member_ids))})
"""
member_df = pd.read_sql(member_sql, connection, params=member_ids)
print(f" 查询到 {len(member_df)} 个客户信息")
print("步骤3/4: 查询开单信息和消费/退卡/扣除数据...")
# 查询开单信息
billing_ids = base_df['开单编号'].dropna().unique().tolist()
if billing_ids:
billing_sql = f"""
SELECT
kd.F_Id AS 开单编号,
COALESCE(kd.khly, '') AS 客户来源,
kd.kdrq AS 开单日期
FROM lq_kd_kdjlb kd
WHERE kd.F_Id IN ({','.join(['%s'] * len(billing_ids))})
"""
billing_df = pd.read_sql(billing_sql, connection, params=billing_ids)
else:
billing_df = pd.DataFrame(columns=['开单编号', '客户来源', '开单日期'])
print(f" 查询到 {len(billing_df)} 条开单信息")
# 查询已消费数据
item_ids = base_df['开单品项明细ID'].unique().tolist()
if item_ids:
consumed_sql = f"""
SELECT
F_BillingItemId AS 开单品项明细ID,
SUM(F_OriginalProjectNumber) AS 已消费次数
FROM lq_xh_pxmx
WHERE F_IsEffective = 1
AND F_BillingItemId IN ({','.join(['%s'] * len(item_ids))})
GROUP BY F_BillingItemId
"""
consumed_df = pd.read_sql(consumed_sql, connection, params=item_ids)
else:
consumed_df = pd.DataFrame(columns=['开单品项明细ID', '已消费次数'])
print(f" 查询到 {len(consumed_df)} 条消费记录")
# 查询已退卡数据
if item_ids:
refunded_sql = f"""
SELECT
F_BillingItemId AS 开单品项明细ID,
SUM(F_ProjectNumber) AS 已退卡次数
FROM lq_hytk_mx
WHERE F_IsEffective = 1
AND F_BillingItemId IN ({','.join(['%s'] * len(item_ids))})
GROUP BY F_BillingItemId
"""
refunded_df = pd.read_sql(refunded_sql, connection, params=item_ids)
else:
refunded_df = pd.DataFrame(columns=['开单品项明细ID', '已退卡次数'])
print(f" 查询到 {len(refunded_df)} 条退卡记录")
# 查询已扣除数据
if item_ids:
deducted_sql = f"""
SELECT
F_DeductId AS 开单品项明细ID,
SUM(F_ProjectNumber) AS 已扣除次数
FROM lq_kd_deductinfo
WHERE F_IsEffective = 1
AND F_DeductId IN ({','.join(['%s'] * len(item_ids))})
GROUP BY F_DeductId
"""
deducted_df = pd.read_sql(deducted_sql, connection, params=item_ids)
else:
deducted_df = pd.DataFrame(columns=['开单品项明细ID', '已扣除次数'])
print(f" 查询到 {len(deducted_df)} 条扣除记录")
print("步骤4/4: 合并数据并计算剩余权益...")
# 合并数据
df = base_df.merge(member_df, on='会员ID', how='left')
df = df.merge(billing_df, on='开单编号', how='left')
df = df.merge(consumed_df, on='开单品项明细ID', how='left')
df = df.merge(refunded_df, on='开单品项明细ID', how='left')
df = df.merge(deducted_df, on='开单品项明细ID', how='left')
# 填充客户信息空值(如果客户信息缺失,至少显示会员ID)
df['客户编号'] = df['客户编号'].fillna(df['会员ID'])
df['客户档案号'] = df['客户档案号'].fillna('')
df['客户姓名'] = df['客户姓名'].fillna('')
df['手机号'] = df['手机号'].fillna('')
df['归属门店'] = df['归属门店'].fillna('')
df['客户来源'] = df['客户来源'].fillna('')
# 填充空值
df['已消费次数'] = df['已消费次数'].fillna(0)
df['已退卡次数'] = df['已退卡次数'].fillna(0)
df['已扣除次数'] = df['已扣除次数'].fillna(0)
# 计算剩余次数和剩余价值
df['剩余次数'] = df['总购买次数'] - df['已消费次数'] - df['已退卡次数'] - df['已扣除次数']
df['剩余价值'] = df['单价'] * df['剩余次数']
# 过滤剩余次数大于0的记录
df = df[df['剩余次数'] > 0].copy()
# 重命名列
df = df.rename(columns={
'客户档案号': '客户档案号',
'客户姓名': '客户姓名',
'手机号': '手机号',
'注册时间': '注册时间',
'归属门店': '归属门店',
'客户来源': '客户来源',
'品项名称': '品项名称',
'品项ID': '品项ID',
'品项分类': '品项分类',
'科美类型': '科美类型',
'业绩类型': '业绩类型',
'来源类型': '来源类型',
'单价': '单价',
'总购买次数': '总购买次数',
'已消费次数': '已消费次数',
'已退卡次数': '已退卡次数',
'已扣除次数': '已扣除次数',
'剩余次数': '剩余次数',
'剩余价值': '剩余价值',
'总金额': '总金额',
'实付金额': '实付金额',
'开单日期': '开单日期',
'业绩时间': '业绩时间',
'备注': '备注'
})
# 选择需要的列(客户编号放在最前面)
df = df[[
'客户编号', '客户档案号', '客户姓名', '手机号', '注册时间', '归属门店', '客户来源',
'品项名称', '品项ID', '品项分类', '科美类型', '业绩类型', '来源类型',
'单价', '总购买次数', '已消费次数', '已退卡次数', '已扣除次数', '剩余次数', '剩余价值',
'总金额', '实付金额', '开单日期', '业绩时间', '备注'
]]
# 排序
df = df.sort_values(['客户档案号', '品项名称', '来源类型'])
cursor.close()
# 格式化时间字段
if '注册时间' in df.columns:
df['注册时间'] = pd.to_datetime(df['注册时间']).dt.strftime('%Y-%m-%d %H:%M:%S')
df['注册时间'] = df['注册时间'].replace('NaT', '')
if '开单日期' in df.columns:
df['开单日期'] = pd.to_datetime(df['开单日期']).dt.strftime('%Y-%m-%d %H:%M:%S')
df['开单日期'] = df['开单日期'].replace('NaT', '')
if '业绩时间' in df.columns:
df['业绩时间'] = pd.to_datetime(df['业绩时间']).dt.strftime('%Y-%m-%d %H:%M:%S')
df['业绩时间'] = df['业绩时间'].replace('NaT', '')
# 确保数值字段为数值类型
numeric_columns = ['单价', '总购买次数', '已消费次数', '已退卡次数', '已扣除次数', '剩余次数', '剩余价值', '总金额', '实付金额']
for col in numeric_columns:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
return df
except Exception as e:
print(f"查询数据时发生错误: {e}")
raise
finally:
if connection:
connection.close()
def generate_excel(df, output_file='所有用户剩余权益数据.xlsx'):
"""生成Excel文件"""
try:
# 创建Excel写入器
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='剩余权益明细', index=False)
# 获取工作表对象进行格式设置
worksheet = writer.sheets['剩余权益明细']
# 设置列宽
column_widths = {
'A': 20, # 客户编号
'B': 20, # 客户档案号
'C': 15, # 客户姓名
'D': 15, # 手机号
'E': 20, # 注册时间
'F': 20, # 归属门店
'G': 15, # 客户来源
'H': 25, # 品项名称
'I': 20, # 品项ID
'J': 15, # 品项分类
'K': 12, # 科美类型
'L': 12, # 业绩类型
'M': 12, # 来源类型
'N': 12, # 单价
'O': 12, # 总购买次数
'P': 12, # 已消费次数
'Q': 12, # 已退卡次数
'R': 12, # 已扣除次数
'S': 12, # 剩余次数
'T': 15, # 剩余价值
'U': 15, # 总金额
'V': 15, # 实付金额
'W': 20, # 开单日期
'X': 20, # 业绩时间
'Y': 30 # 备注
}
for col, width in column_widths.items():
worksheet.column_dimensions[col].width = width
# 设置表头样式
from openpyxl.styles import Font, Alignment, PatternFill
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
header_font = Font(bold=True, color='FFFFFF', size=11)
for cell in worksheet[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center', vertical='center')
# 设置数据对齐方式
for row in worksheet.iter_rows(min_row=2, max_row=worksheet.max_row):
for cell in row:
col_letter = cell.column_letter
# 数值列右对齐
if col_letter in ['N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V']:
cell.alignment = Alignment(horizontal='right', vertical='center')
else:
cell.alignment = Alignment(horizontal='left', vertical='center')
print(f"Excel文件已生成: {output_file}")
print(f"共 {len(df)} 条记录")
return output_file
except Exception as e:
print(f"生成Excel文件时发生错误: {e}")
raise
def main():
"""主函数"""
print("开始查询所有用户的剩余权益数据...")
df = query_data()
if df.empty:
print("未查询到数据")
return
print(f"查询到 {len(df)} 条记录")
print("\n前5条数据预览:")
print(df.head())
# 统计信息
print(f"\n统计信息:")
print(f"- 总客户数: {df['客户档案号'].nunique()}")
print(f"- 总品项数: {df['品项名称'].nunique()}")
print(f"- 剩余权益总价值: {df['剩余价值'].sum():,.2f} 元")
print(f"- 剩余权益总次数: {df['剩余次数'].sum():,.0f} 次")
# 生成Excel文件
output_file = f"所有用户剩余权益数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
generate_excel(df, output_file)
print(f"\n文件保存路径: {os.path.abspath(output_file)}")
if __name__ == '__main__':
main()