import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go import io # Month order for Persian calendar MONTH_ORDER = [ "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند" ] # Available charts list AVAILABLE_CHARTS = [ "مقایسه روند فروش ماهانه (ناخالص، خالص با/بدون ارزش افزوده)", "روند فروش ماهانه بر اساس فروش ناخالص", "روند فروش ماهانه بر اساس فروش خالص (بدون ارزش افزوده)", "روند فروش ماهانه بر اساس فروش خالص (با ارزش افزوده)", "تحلیل کالاها (80/20)", "محصولات با بیشترین مرجوعی", "گروه محصولات پرفروش", "ده تولید کننده پرفروش", "نرخ مرجوعی بر اساس برند (درصد)", "سهم فروش گروه محصولات در ماه", "تحلیل همبستگی تخفیف و فروش", "میانگین ارزش هر فاکتور (ریالی)", "ماتریس همبستگی متغیرها (Heatmap)" ] def read_data_file(file_obj): """Read data file based on its format.""" file_name = file_obj.name file_extension = file_name.split('.')[-1].lower() if file_extension == 'csv': try: return pd.read_csv(file_obj, encoding='utf-8') except UnicodeDecodeError: file_obj.seek(0) try: return pd.read_csv(file_obj, encoding='windows-1256') except: file_obj.seek(0) return pd.read_csv(file_obj, encoding='latin1') elif file_extension in ['xls', 'xlsx']: return pd.read_excel(file_obj) else: raise ValueError("فرمت فایل پشتیبانی نمی‌شود") def load_and_process_data(file_obj): """Upload file and prepare data with Persian headers.""" try: df = read_data_file(file_obj) # Standardize column names rename_map = { "نام تجاری": "نام برند", "گروه کامل کالا": "گروه کامل کالا", "Net_Sales_Amount": "مبلغ فروش خالص (با مالیات بر ارزش افزوده)", "ناخالص فروش با کسر تخفیف": "مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", "ناخالص فروش با کسر تخفيف": "مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", "مبلغ فروش خالص": "مبلغ فروش خالص (با مالیات بر ارزش افزوده)" } df.rename(columns=rename_map, inplace=True) # Validate required columns required_columns = [ "ماه", "کد کالا", "نام برند", "تولید کننده", "نام کالا", "مبلغ فروش ناخالص", "مبلغ تخفیف", "مبلغ فروش خالص (با مالیات بر ارزش افزوده)", "مبلغ برگشتی خالص", "مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)" ] missing_cols = [col for col in required_columns if col not in df.columns] if missing_cols: return { 'success': False, 'error': f"ستون‌های زیر در فایل یافت نشدند: {', '.join(missing_cols)}", 'data': None, 'kpis': None } # Clean data df["ماه"] = df["ماه"].astype(str) present_months = [m for m in MONTH_ORDER if m in df["ماه"].unique()] if present_months: df["ماه"] = pd.Categorical(df["ماه"], categories=MONTH_ORDER, ordered=True) numeric_cols = [ "مبلغ فروش ناخالص", "مبلغ فروش خالص (با مالیات بر ارزش افزوده)", "مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", "مبلغ برگشتی خالص", "تعداد فاکتور", "مبلغ تخفیف" ] for col in numeric_cols: if col in df.columns: df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0) if "گروه محصول" in df.columns: df["Analyzed_Group"] = df["گروه محصول"] else: df["Analyzed_Group"] = df["نام برند"] # Calculate KPIs kpis = { 'net_sale_without_VAT': df["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum(), 'net_sale_with_VAT': df["مبلغ فروش خالص (با مالیات بر ارزش افزوده)"].sum(), 'gross_sales': df["مبلغ فروش ناخالص"].sum(), 'total_product': df["کد کالا"].nunique(), 'total_brand': df["نام برند"].nunique(), 'total_return': df["مبلغ برگشتی خالص"].sum() } return { 'success': True, 'error': None, 'data': df, 'kpis': kpis } except Exception as e: return { 'success': False, 'error': str(e), 'data': None, 'kpis': None } def create_specific_chart(chart_name, df_state): """Create specific chart based on selection.""" if df_state is None or df_state.empty: fig = go.Figure() fig.update_layout( title="لطفا ابتدا فایل داده را بارگذاری کنید 📂", xaxis={"visible": False}, yaxis={"visible": False}, annotations=[{ "text": "No Data", "xref": "paper", "yref": "paper", "showarrow": False, "font": {"size": 20} }] ) return fig df = df_state.copy() fig = None Y_LABEL_GROSS = 'مبلغ فروش ناخالص (ریال)' Y_LABEL_NET_NO_TAX = 'مبلغ فروش خالص بدون ارزش افزوده (ریال)' Y_LABEL_NET = 'مبلغ فروش خالص (ریال)' X_LABEL_MONTH = 'ماه' try: if chart_name == "مقایسه روند فروش ماهانه (ناخالص، خالص با/بدون ارزش افزوده)": cols_to_agg = { "مبلغ فروش ناخالص": "sum", "مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)": "sum", "مبلغ فروش خالص (با مالیات بر ارزش افزوده)": "sum" } monthly = df.groupby("ماه").agg(cols_to_agg).reset_index().sort_values("ماه") monthly_long = monthly.melt( id_vars='ماه', var_name='نوع فروش', value_name='مبلغ' ) fig = px.line( monthly_long, x="ماه", y="مبلغ", color='نوع فروش', markers=True, labels={'ماه': X_LABEL_MONTH, 'مبلغ': 'مبلغ فروش (ریال)', 'نوع فروش': 'شاخص فروش'} ) fig.update_layout( title={"text": "مقایسه جامع روند ماهانه شاخص‌های فروش", "x": 0.5, "y": 0.95}, legend_title_text='شاخص فروش', legend=dict(orientation="h", y=-0.2) ) elif chart_name == "روند فروش ماهانه بر اساس فروش ناخالص": monthly = df.groupby("ماه")["مبلغ فروش ناخالص"].sum().reset_index().sort_values("ماه") fig = px.line( monthly, x="ماه", y="مبلغ فروش ناخالص", markers=True, labels={'ماه': X_LABEL_MONTH, 'مبلغ فروش ناخالص': Y_LABEL_GROSS} ) fig.update_layout(title={"text": "روند فروش بر اساس فروش ناخالص", "x": 0.5, "y": 0.95}) elif chart_name == "روند فروش ماهانه بر اساس فروش خالص (بدون ارزش افزوده)": monthly = df.groupby("ماه")["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum().reset_index().sort_values("ماه") fig = px.line( monthly, x="ماه", y="مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", markers=True, labels={'ماه': X_LABEL_MONTH, 'مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)': Y_LABEL_NET_NO_TAX} ) fig.update_layout(title={"text": "روند فروش خالص بدون ارزش افزوده ماهانه", "x": 0.5, "y": 0.95}) elif chart_name == "روند فروش ماهانه بر اساس فروش خالص (با ارزش افزوده)": col_name = "مبلغ فروش خالص (با مالیات بر ارزش افزوده)" monthly = df.groupby("ماه")[col_name].sum().reset_index().sort_values("ماه") fig = px.line( monthly, x="ماه", y=col_name, markers=True, labels={'ماه': X_LABEL_MONTH, col_name: Y_LABEL_NET} ) fig.update_layout(title={"text": "روند خالص فروش ماهانه (با ارزش افزوده)", "x": 0.5, "y": 0.95}) elif chart_name == "تحلیل کالاها (80/20)": sales_data = df.groupby("نام کالا")["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum().reset_index() sales_data = sales_data[sales_data['مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)'] > 0] sales_data = sales_data.sort_values("مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", ascending=False).reset_index(drop=True) if sales_data.empty: return go.Figure().update_layout(title="هیچ فروش مثبتی برای تحلیل پارتو وجود ندارد") sales_data['Cumulative %'] = 100 * ( sales_data["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].cumsum() / sales_data["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum() ) over_80_subset = sales_data[sales_data['Cumulative %'] >= 80] pareto_rank = over_80_subset.index[0] + 1 if not over_80_subset.empty else len(sales_data) total_product = df["کد کالا"].nunique() display_count = max(20, pareto_rank + 3) top_sales = sales_data.head(display_count) fig = go.Figure() fig.add_trace(go.Bar( x=top_sales["نام کالا"], y=top_sales["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"], name="فروش", marker_color='#667eea' )) fig.add_trace(go.Scatter( x=top_sales["نام کالا"], y=top_sales["Cumulative %"], name="تجمعی %", yaxis="y2", mode="lines+markers", marker_color='#f5576c' )) fig.add_shape( type="line", xref="paper", yref="y2", x0=0, y0=80, x1=1, y1=80, line=dict(color="red", width=2, dash="dash") ) fig.update_layout( title={"text": f"تحلیل پارتو: {pareto_rank} کالا از سبد {total_product} کالا 80% فروش را تامین میکند", "x": 0.5, "y": 0.95}, xaxis_title="نام کالا", yaxis_title="مبلغ فروش خالص (بدون ارزش افزوده) (ریال)", yaxis2=dict(title="درصد تجمعی", overlaying="y", side="right", range=[0, 105]), legend=dict(x=0.4, y=0.5, orientation='h'), barmode='overlay' ) elif chart_name == "محصولات با بیشترین مرجوعی": top_returns = df.groupby("نام کالا")["مبلغ برگشتی خالص"].sum().abs().reset_index() top_returns = top_returns[top_returns["مبلغ برگشتی خالص"] > 0] if not top_returns.empty: top_returns = top_returns.sort_values("مبلغ برگشتی خالص", ascending=False).head(10) fig = px.bar( top_returns, x="مبلغ برگشتی خالص", y="نام کالا", orientation='h', color_discrete_sequence=['#f5576c'] ) fig.update_layout( title={"text": "محصولات با بیشترین مرجوعی", "x": 0.5, "y": 0.95}, yaxis={'categoryorder': 'total ascending'} ) fig.update_xaxes(title="مبلغ مرجوعی (ریال)") else: fig = go.Figure().update_layout(title="هیچ مرجوعی ثبت نشده است") elif chart_name == "گروه محصولات پرفروش": top_group = df.groupby("Analyzed_Group")["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum().reset_index() top_group = top_group.sort_values("مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", ascending=False).head(10) fig = px.bar( top_group, x="مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", y="Analyzed_Group", orientation="h", color_discrete_sequence=['#667eea'] ) fig.update_layout( title={"text": "گروه محصولات پرفروش", "x": 0.5, "y": 0.95}, yaxis={'categoryorder': 'total ascending'} ) elif chart_name == "ده تولید کننده پرفروش": top_prod = df.groupby("تولید کننده")["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum().reset_index() top_prod = top_prod.sort_values("مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", ascending=False).head(10) fig = px.bar( top_prod, x="مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", y="تولید کننده", orientation="h", color_discrete_sequence=['#38ef7d'] ) fig.update_layout( title={"text": "ده تولید کننده پرفروش", "x": 0.5, "y": 0.95}, yaxis={'categoryorder': 'total ascending'} ) elif chart_name == "نرخ مرجوعی بر اساس برند (درصد)": brand_stats = df.groupby("نام برند")[["مبلغ برگشتی خالص", "مبلغ فروش ناخالص"]].sum().reset_index() brand_stats = brand_stats[brand_stats["مبلغ فروش ناخالص"] > 0] brand_stats["Return_Rate"] = (brand_stats["مبلغ برگشتی خالص"] / brand_stats["مبلغ فروش ناخالص"]) * 100 brand_stats = brand_stats[brand_stats["مبلغ فروش ناخالص"] > 10000000] brand_stats = brand_stats.sort_values("Return_Rate", ascending=False).head(15) fig = px.bar( brand_stats, x="Return_Rate", y="نام برند", orientation='h', color="Return_Rate", color_continuous_scale="Reds", hover_data={"Return_Rate": ":,.2f"} ) fig.update_layout( title={"text": "درصد نرخ مرجوعی بر اساس برند", "x": 0.5, "y": 0.95}, yaxis={'categoryorder': 'total ascending'} ) fig.update_xaxes(title="درصد مرجوعی از فروش (%)") fig.update_yaxes(title="نام برند") elif chart_name == "سهم فروش گروه محصولات در ماه": cat_trend = df.groupby(["ماه", "Analyzed_Group"])["مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)"].sum().reset_index() fig = px.bar( cat_trend, x="ماه", y="مبلغ فروش خالص (بدون مالیات بر ارزش افزوده)", color="Analyzed_Group", title="ترکیب فروش ماهانه بر اساس گروه محصول" ) fig.update_layout(title={"x": 0.5, "y": 0.95}, barmode='stack') fig.update_yaxes(title="فروش (ریال)") elif chart_name == "تحلیل همبستگی تخفیف و فروش": prod_perf = df.groupby("نام کالا")[["مبلغ فروش ناخالص", "مبلغ تخفیف", "تعداد فاکتور"]].sum().reset_index() prod_perf = prod_perf[prod_perf["مبلغ فروش ناخالص"] > 0] prod_perf["Discount_Pct"] = (prod_perf["مبلغ تخفیف"] / prod_perf["مبلغ فروش ناخالص"]) * 100 prod_perf = prod_perf[prod_perf["Discount_Pct"] <= 100] fig = px.scatter( prod_perf, x="مبلغ فروش ناخالص", y="Discount_Pct", size="تعداد فاکتور", hover_name="نام کالا", color="Discount_Pct", color_continuous_scale="Viridis", title="آیا تخفیف بیشتر منجر به فروش بیشتر می‌شود؟", hover_data={ "مبلغ فروش ناخالص": ":,.2f", "Discount_Pct": ":.2f", "تعداد فاکتور": ":,.0f" } ) fig.update_layout(title={"x": 0.5, "y": 0.95}) fig.update_xaxes(title="مبلغ فروش ناخالص (ریال)") fig.update_yaxes(title="درصد تخفیف اعطا شده (%)") elif chart_name == "میانگین ارزش هر فاکتور (ریالی)": col_name = "مبلغ فروش خالص (با مالیات بر ارز