Spaces:
Running
Running
| # (c) Timeless Wind Publishing // Proprietary Information | |
| import streamlit as st | |
| import tempfile | |
| from pathlib import Path | |
| import hashlib | |
| import pandas as pd | |
| import io | |
| from accounting_program import generate_report | |
| from openpyxl.styles import Font, PatternFill | |
| import os | |
| # Password protection | |
| def check_password(): | |
| """Returns `True` if the user has entered the correct password.""" | |
| def password_entered(): | |
| """Checks whether a password entered by the user is correct.""" | |
| # Hash the password for security | |
| entered_password = st.session_state["password"] | |
| # Change this to your desired password (hashed) | |
| # To generate hash: hashlib.sha256("your_password".encode()).hexdigest() | |
| correct_password_hash = os.environ.get("PASSWORD_HASH") | |
| if hashlib.sha256(entered_password.encode()).hexdigest() == correct_password_hash: | |
| st.session_state["password_correct"] = True | |
| del st.session_state["password"] # Don't store password | |
| else: | |
| st.session_state["password_correct"] = False | |
| # First run or password not correct | |
| if "password_correct" not in st.session_state: | |
| st.text_input( | |
| "π Enter Password", | |
| type="password", | |
| on_change=password_entered, | |
| key="password" | |
| ) | |
| st.info("Please enter the password to access the application.") | |
| return False | |
| elif not st.session_state["password_correct"]: | |
| st.text_input( | |
| "π Enter Password", | |
| type="password", | |
| on_change=password_entered, | |
| key="password" | |
| ) | |
| st.error("π Password incorrect") | |
| return False | |
| else: | |
| return True | |
| def create_download_dataframe(final_royalties, include_total=True): | |
| """Create a formatted dataframe for download""" | |
| grouped = final_royalties.groupby(['Author', 'Title'], as_index=False)['Adjusted_Earnings'].sum() | |
| # Create download dataframe | |
| download_df = grouped.copy() | |
| download_df.columns = ['Author', 'Title', 'Earnings'] | |
| # Add a total row only if requested (for CSV) | |
| if include_total: | |
| total_earnings = final_royalties["Adjusted_Earnings"].sum() | |
| total_row = pd.DataFrame({ | |
| 'Author': ['TOTAL'], | |
| 'Title': [''], | |
| 'Earnings': [total_earnings] | |
| }) | |
| download_df = pd.concat([download_df, total_row], ignore_index=True) | |
| return download_df | |
| def display_report(royalties_path, payments_path): | |
| """Display the accounting report in Streamlit format""" | |
| final_royalties = generate_report(royalties_path, payments_path) | |
| # Store in session state for download | |
| st.session_state['report_data'] = final_royalties | |
| grouped = final_royalties.groupby(['Author', 'Title'], as_index=False)['Adjusted_Earnings'].sum() | |
| # Display total earnings | |
| total_earnings = final_royalties["Adjusted_Earnings"].sum() | |
| st.subheader("π Report Summary") | |
| st.metric("Total Earnings", f"${total_earnings:,.2f}") | |
| st.divider() | |
| # Display earnings by author | |
| st.subheader("π Earnings by Author") | |
| for author, group in grouped.groupby('Author'): | |
| author_total = group['Adjusted_Earnings'].sum() | |
| with st.expander(f"**{author}** - ${author_total:,.2f}", expanded=True): | |
| # Create a dataframe for this author's books | |
| books_data = [] | |
| for _, row in group.iterrows(): | |
| books_data.append({ | |
| 'Title': row['Title'], | |
| 'Earnings': f"${row['Adjusted_Earnings']:,.2f}" | |
| }) | |
| # Display as a clean table | |
| st.dataframe( | |
| books_data, | |
| use_container_width=True, | |
| hide_index=True | |
| ) | |
| st.caption(f"Total for {author}: ${author_total:,.2f}") | |
| # Download section | |
| st.divider() | |
| st.subheader("π₯ Download Report") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Excel download with pivot table | |
| excel_buffer = io.BytesIO() | |
| # Create dataframe without total row for Excel | |
| download_df_excel = create_download_dataframe(final_royalties, include_total=False) | |
| # Create a pivot table summary using pandas | |
| pivot_df = download_df_excel.copy() | |
| # Create author subtotals | |
| author_totals = pivot_df.groupby('Author')['Earnings'].sum().reset_index() | |
| author_totals['Title'] = 'SUBTOTAL' | |
| # Sort and combine data with subtotals | |
| pivot_display = [] | |
| for author in pivot_df['Author'].unique(): | |
| # Add books for this author | |
| author_books = pivot_df[pivot_df['Author'] == author].copy() | |
| for _, row in author_books.iterrows(): | |
| pivot_display.append({ | |
| 'Author': row['Author'], | |
| 'Title': row['Title'], | |
| 'Earnings': row['Earnings'] | |
| }) | |
| # Add subtotal row | |
| author_total = author_totals[author_totals['Author'] == author]['Earnings'].values[0] | |
| pivot_display.append({ | |
| 'Author': author, | |
| 'Title': ' β Subtotal', | |
| 'Earnings': author_total | |
| }) | |
| # Add grand total | |
| grand_total = pivot_df['Earnings'].sum() | |
| pivot_display.append({ | |
| 'Author': 'GRAND TOTAL', | |
| 'Title': '', | |
| 'Earnings': grand_total | |
| }) | |
| pivot_summary = pd.DataFrame(pivot_display) | |
| with pd.ExcelWriter(excel_buffer, engine='openpyxl') as writer: | |
| # Write main data sheet | |
| download_df_excel.to_excel(writer, index=False, sheet_name='Earnings Data') | |
| # Write pivot table summary | |
| pivot_summary.to_excel(writer, index=False, sheet_name='Pivot Summary') | |
| # Format the pivot summary sheet | |
| workbook = writer.book | |
| pivot_sheet = workbook['Pivot Summary'] | |
| # Bold the header row | |
| for cell in pivot_sheet[1]: | |
| cell.font = cell.font.copy(bold=True) | |
| # Bold subtotal and grand total rows | |
| for row in pivot_sheet.iter_rows(min_row=2): | |
| if row[1].value and ('Subtotal' in str(row[1].value) or row[0].value == 'GRAND TOTAL'): | |
| for cell in row: | |
| cell.font = Font(bold=True) | |
| if row[0].value == 'GRAND TOTAL': | |
| cell.fill = PatternFill(start_color='E0E0E0', end_color='E0E0E0', fill_type='solid') | |
| excel_buffer.seek(0) | |
| st.download_button( | |
| label="π Download as Excel", | |
| data=excel_buffer, | |
| file_name="twp_earnings_report.xlsx", | |
| mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", | |
| use_container_width=True | |
| ) | |
| with col2: | |
| # CSV download with total row | |
| download_df_csv = create_download_dataframe(final_royalties, include_total=True) | |
| csv_buffer = io.StringIO() | |
| download_df_csv.to_csv(csv_buffer, index=False) | |
| st.download_button( | |
| label="π Download as CSV", | |
| data=csv_buffer.getvalue(), | |
| file_name="twp_earnings_report.csv", | |
| mime="text/csv", | |
| use_container_width=True | |
| ) | |
| def main(): | |
| # Check password first | |
| if not check_password(): | |
| st.stop() # Don't continue if password is wrong | |
| st.title("TWP Accounting Report Generator") | |
| st.write("Upload your royalties and payouts files to generate a report") | |
| # Create two columns for file uploaders | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| royalties_file = st.file_uploader( | |
| "Upload Royalties File", | |
| type=['csv', 'xlsx', 'xls'], | |
| help="Select your royalties data file" | |
| ) | |
| with col2: | |
| payouts_file = st.file_uploader( | |
| "Upload Payouts File", | |
| type=['csv', 'xlsx', 'xls'], | |
| help="Select your payouts data file" | |
| ) | |
| # Process button | |
| if st.button("Generate Report", type="primary", disabled=not (royalties_file and payouts_file)): | |
| if royalties_file and payouts_file: | |
| with st.spinner("Processing files..."): | |
| # Create temporary files to save uploads | |
| with tempfile.TemporaryDirectory() as tmp_dir: | |
| # Save uploaded files to temp directory | |
| royalties_path = Path(tmp_dir) / royalties_file.name | |
| payouts_path = Path(tmp_dir) / payouts_file.name | |
| with open(royalties_path, 'wb') as f: | |
| f.write(royalties_file.getbuffer()) | |
| with open(payouts_path, 'wb') as f: | |
| f.write(payouts_file.getbuffer()) | |
| # Display processing info | |
| st.info(f"Processing: {royalties_file.name} and {payouts_file.name}") | |
| # Generate and display the report | |
| try: | |
| display_report(str(royalties_path), str(payouts_path)) | |
| st.success("Report generated successfully!") | |
| except Exception as e: | |
| st.error(f"Error generating report: {str(e)}") | |
| st.exception(e) | |
| # Instructions | |
| with st.expander("βΉοΈ Instructions"): | |
| st.markdown(""" | |
| 1. Upload your **Royalties File** using the left file uploader | |
| 2. Upload your **Payouts File** using the right file uploader | |
| 3. Click **Generate Report** to process the files | |
| 4. View the results below | |
| 5. Download the report as Excel or CSV using the download buttons | |
| **Supported file formats:** CSV, Excel (.xlsx, .xls) | |
| """) | |
| if __name__ == "__main__": | |
| main() | |