1+ import streamlit as st
2+ import psycopg2
3+ import pandas as pd
4+ from decouple import config
5+ from datetime import datetime
6+ import pandas
7+
8+ # Verify pandas version
9+ if not (1.4 <= float (pandas .__version__ .split ('.' )[0 ]) + float (pandas .__version__ .split ('.' )[1 ])/ 10 < 3 ):
10+ st .error (f"Streamlit requires pandas>=1.4.0,<3, but found pandas=={ pandas .__version__ } " )
11+ st .stop ()
12+
13+ # Database connection function
14+ def get_db_connection ():
15+ try :
16+ conn = psycopg2 .connect (
17+ host = config ('POSTGRES_HOST' , default = 'localhost' ),
18+ port = config ('POSTGRES_PORT' , default = '5432' ),
19+ database = config ('POSTGRES_DB' , default = 'spark_db' ),
20+ user = config ('POSTGRES_USER' , default = 'spark_user' ),
21+ password = config ('POSTGRES_PASSWORD' )
22+ )
23+ return conn
24+ except Exception as e :
25+ st .error (f"Failed to connect to database: { e } " )
26+ return None
27+
28+ # Fetch all customers
29+ @st .cache_data
30+ def get_customers ():
31+ conn = get_db_connection ()
32+ if conn is None :
33+ return []
34+ try :
35+ with conn .cursor () as cur :
36+ cur .execute ("SELECT CustomerID FROM Customer ORDER BY CustomerID" )
37+ customers = [row [0 ] for row in cur .fetchall ()]
38+ conn .close ()
39+ return customers
40+ except Exception as e :
41+ st .error (f"Error fetching customers: { e } " )
42+ conn .close ()
43+ return []
44+
45+ # Calculate total spend for a customer
46+ def get_total_spend (customer_id ):
47+ conn = get_db_connection ()
48+ if conn is None :
49+ return 0.0
50+ try :
51+ with conn .cursor () as cur :
52+ cur .execute ("""
53+ SELECT SUM(s.Quantity * s.UnitPrice) as total_spend
54+ FROM Invoice i
55+ JOIN InvoiceStock ist ON i.InvoiceNo = ist.InvoiceNo
56+ JOIN Stock s ON ist.StockID = s.StockID
57+ WHERE i.CustomerID = %s
58+ AND i.InvoiceNo NOT LIKE 'C%%'
59+ """ , (customer_id ,))
60+ result = cur .fetchone ()
61+ total_spend = result [0 ] if result [0 ] is not None else 0.0
62+ conn .close ()
63+ return total_spend
64+ except Exception as e :
65+ st .error (f"Error calculating total spend: { e } " )
66+ conn .close ()
67+ return 0.0
68+
69+ # Fetch order history for a customer
70+ def get_order_history (customer_id ):
71+ conn = get_db_connection ()
72+ if conn is None :
73+ return pd .DataFrame ()
74+ try :
75+ query = """
76+ SELECT i.InvoiceNo, i.InvoiceDate, SUM(s.Quantity * s.UnitPrice) as Amount,
77+ SUM(s.Quantity) as Quantity,
78+ STRING_AGG(s.Description, ', ') as ItemsPurchased
79+ FROM Invoice i
80+ JOIN InvoiceStock ist ON i.InvoiceNo = ist.InvoiceNo
81+ JOIN Stock s ON ist.StockID = s.StockID
82+ WHERE i.CustomerID = %s
83+ GROUP BY i.InvoiceNo, i.InvoiceDate
84+ ORDER BY i.InvoiceDate DESC
85+ """
86+ df = pd .read_sql_query (query , conn , params = (customer_id ,))
87+ conn .close ()
88+ return df
89+ except Exception as e :
90+ st .error (f"Error fetching order history: { e } " )
91+ conn .close ()
92+ return pd .DataFrame ()
93+
94+ # Fetch invoice details
95+ def get_invoice_details (invoice_no ):
96+ conn = get_db_connection ()
97+ if conn is None :
98+ return pd .DataFrame ()
99+ try :
100+ query = """
101+ SELECT i.InvoiceNo, i.InvoiceDate, i.CustomerID, c.Country_Name,
102+ s.StockCode, s.Description, s.Quantity, s.UnitPrice,
103+ (s.Quantity * s.UnitPrice) as Total
104+ FROM Invoice i
105+ JOIN InvoiceStock ist ON i.InvoiceNo = ist.InvoiceNo
106+ JOIN Stock s ON ist.StockID = s.StockID
107+ LEFT JOIN Country c ON i.CustomerID = c.CustomerID
108+ WHERE i.InvoiceNo = %s
109+ """
110+ df = pd .read_sql_query (query , conn , params = (invoice_no ,))
111+ conn .close ()
112+ return df
113+ except Exception as e :
114+ st .error (f"Error fetching invoice details: { e } " )
115+ conn .close ()
116+ return pd .DataFrame ()
117+
118+ # Fetch returned items (invoices starting with 'C')
119+ def get_returned_items (customer_id ):
120+ conn = get_db_connection ()
121+ if conn is None :
122+ return pd .DataFrame ()
123+ try :
124+ query = """
125+ SELECT i.InvoiceNo, i.InvoiceDate, s.StockCode, s.Description,
126+ s.Quantity, s.UnitPrice, (s.Quantity * s.UnitPrice) as Total
127+ FROM Invoice i
128+ JOIN InvoiceStock ist ON i.InvoiceNo = ist.InvoiceNo
129+ JOIN Stock s ON ist.StockID = s.StockID
130+ WHERE i.CustomerID = %s AND i.InvoiceNo LIKE 'C%%'
131+ ORDER BY i.InvoiceDate DESC
132+ """
133+ df = pd .read_sql_query (query , conn , params = (customer_id ,))
134+ conn .close ()
135+ return df
136+ except Exception as e :
137+ st .error (f"Error fetching returned items: { e } " )
138+ conn .close ()
139+ return pd .DataFrame ()
140+
141+ # Streamlit app
142+ st .title ("Customer Dashboard" )
143+
144+ # Sidebar for customer selection
145+ st .sidebar .header ("Select Customer" )
146+ customer_ids = get_customers ()
147+ if not customer_ids :
148+ st .sidebar .error ("No customers found. Please check database connection." )
149+ st .stop ()
150+
151+ customer_id = st .sidebar .selectbox ("Customer ID" , customer_ids )
152+
153+ # Total Spend
154+ st .header (f"Total Spend for Customer { customer_id } " )
155+ total_spend = get_total_spend (customer_id )
156+ st .metric ("Total Spend (Excluding Returns)" , f"£{ total_spend :,.2f} " )
157+
158+ # Order History
159+ st .header ("Order History" )
160+ order_history = get_order_history (customer_id )
161+ if not order_history .empty :
162+ st .dataframe (
163+ order_history .rename (columns = {
164+ "invoiceno" : "Invoice No" ,
165+ "invoicedate" : "Date" ,
166+ "amount" : "Amount (£)" ,
167+ "quantity" : "Quantity" ,
168+ "itemspurchased" : "Items Purchased"
169+ }),
170+ use_container_width = True
171+ )
172+ else :
173+ st .write ("No orders found for this customer." )
174+
175+ # Query Invoice Details
176+ st .header ("Query Invoice Details" )
177+ invoice_nos = order_history ["invoiceno" ].tolist () if not order_history .empty else []
178+ selected_invoice = st .selectbox ("Select Invoice No" , ["Select an invoice" ] + invoice_nos )
179+ if selected_invoice != "Select an invoice" :
180+ invoice_details = get_invoice_details (selected_invoice )
181+ if not invoice_details .empty :
182+ st .dataframe (
183+ invoice_details .rename (columns = {
184+ "invoiceno" : "Invoice No" ,
185+ "invoicedate" : "Date" ,
186+ "customerid" : "Customer ID" ,
187+ "country_name" : "Country" ,
188+ "stockcode" : "Stock Code" ,
189+ "description" : "Description" ,
190+ "quantity" : "Quantity" ,
191+ "unitprice" : "Unit Price (£)" ,
192+ "total" : "Total (£)"
193+ }),
194+ use_container_width = True
195+ )
196+ else :
197+ st .write ("No details found for this invoice." )
198+
199+ # Returned Items
200+ st .header ("Returned Items" )
201+ returned_items = get_returned_items (customer_id )
202+ if not returned_items .empty :
203+ st .dataframe (
204+ returned_items .rename (columns = {
205+ "invoiceno" : "Invoice No" ,
206+ "invoicedate" : "Date" ,
207+ "stockcode" : "Stock Code" ,
208+ "description" : "Description" ,
209+ "quantity" : "Quantity" ,
210+ "unitprice" : "Unit Price (£)" ,
211+ "total" : "Total (£)"
212+ }),
213+ use_container_width = True
214+ )
215+ else :
216+ st .write ("No returned items found for this customer." )
217+
218+ # Generate Invoice Button (Placeholder)
219+ st .header ("Generate Invoice" )
220+ if st .button ("Generate Invoice" ):
221+ st .info ("Invoice generation is not yet implemented. This will store invoices in blob storage and update Stock.invoice_url." )
0 commit comments