export_all_member_remaining_rights.py 13.9 KB
#!/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()