Spaces:
Running
Running
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +83 -68
src/streamlit_app.py
CHANGED
|
@@ -1176,21 +1176,59 @@ elif len(st.session_state.batch_results) > 0:
|
|
| 1176 |
"Quantity": it.get("Quantity", it.get("Item Quantity", 0)),
|
| 1177 |
"Unit Price": it.get("Unit Price", it.get("Item Unit Price", 0.0)),
|
| 1178 |
"Amount": it.get("Amount", it.get("Item Amount", 0.0)),
|
|
|
|
| 1179 |
"Tax": it.get("Tax", it.get("Item Tax", 0.0)),
|
| 1180 |
"Line Total": it.get("Line Total", it.get("Item Line Total", 0.0)),
|
| 1181 |
})
|
| 1182 |
|
| 1183 |
items_df = pd.DataFrame(normalized) if normalized else pd.DataFrame(
|
| 1184 |
-
columns=["Description", "Quantity", "Unit Price", "Amount", "Tax", "Line Total"]
|
| 1185 |
)
|
| 1186 |
|
| 1187 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
edited_df = st.data_editor(
|
| 1189 |
items_df,
|
| 1190 |
num_rows="dynamic",
|
| 1191 |
key=f"items_editor_{selected_hash}",
|
| 1192 |
use_container_width=True,
|
| 1193 |
height=DATA_EDITOR_HEIGHT - 50, # Reduce height slightly for totals below
|
|
|
|
| 1194 |
)
|
| 1195 |
|
| 1196 |
# Display non-editable totals row immediately below (looks integrated)
|
|
@@ -1205,6 +1243,7 @@ elif len(st.session_state.batch_results) > 0:
|
|
| 1205 |
"Quantity": "",
|
| 1206 |
"Unit Price": "",
|
| 1207 |
"Amount": f"${total_amount:,.2f}",
|
|
|
|
| 1208 |
"Tax": f"${total_tax:,.2f}",
|
| 1209 |
"Line Total": f"${total_line_total:,.2f}"
|
| 1210 |
}])
|
|
@@ -1219,40 +1258,42 @@ elif len(st.session_state.batch_results) > 0:
|
|
| 1219 |
saved = st.form_submit_button("💾 Save All Edits")
|
| 1220 |
# ----------------- FORM END -----------------
|
| 1221 |
|
| 1222 |
-
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
|
| 1227 |
-
|
| 1228 |
-
|
| 1229 |
-
|
| 1230 |
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
| 1237 |
|
| 1238 |
-
|
| 1239 |
-
|
| 1240 |
-
|
| 1241 |
-
|
| 1242 |
-
|
| 1243 |
-
|
| 1244 |
|
| 1245 |
-
|
| 1246 |
-
|
| 1247 |
-
|
| 1248 |
-
|
| 1249 |
-
|
| 1250 |
|
| 1251 |
-
|
| 1252 |
-
|
| 1253 |
-
|
| 1254 |
-
|
| 1255 |
|
|
|
|
|
|
|
| 1256 |
updated = {
|
| 1257 |
'Invoice Number': st.session_state.get(f"Invoice Number_{selected_hash}", ''),
|
| 1258 |
'Invoice Date': invoice_date_str,
|
|
@@ -1282,48 +1323,22 @@ elif len(st.session_state.batch_results) > 0:
|
|
| 1282 |
"Address": st.session_state.get(f"Recipient Address_{selected_hash}", '')},
|
| 1283 |
}
|
| 1284 |
|
| 1285 |
-
#
|
| 1286 |
-
st.session_state[f"Subtotal_{selected_hash}"] = calculated_subtotal
|
| 1287 |
-
st.session_state[f"Tax Percentage_{selected_hash}"] = calculated_tax_pct
|
| 1288 |
-
st.session_state[f"Total Tax_{selected_hash}"] = calculated_total_tax
|
| 1289 |
-
st.session_state[f"Total Amount_{selected_hash}"] = calculated_total
|
| 1290 |
-
|
| 1291 |
st.session_state.batch_results[selected_hash]["edited_data"] = updated
|
|
|
|
|
|
|
| 1292 |
st.success(f"✅ Saved: {current['file_name']} | Updated totals: Subtotal=${calculated_subtotal:,.2f}, Tax=${calculated_total_tax:,.2f}, Total=${calculated_total:,.2f}")
|
| 1293 |
-
# REMOVED st.rerun() - This was causing the download button to disappear
|
| 1294 |
-
|
| 1295 |
-
# Per-file CSV download (MOVED OUTSIDE THE FORM - Always visible)
|
| 1296 |
-
d_currency = st.session_state.get(f"Currency_{selected_hash}", 'USD')
|
| 1297 |
-
if d_currency == 'Other':
|
| 1298 |
-
d_currency = st.session_state.get(f"Currency_Custom_{selected_hash}", '')
|
| 1299 |
-
|
| 1300 |
-
# Convert date objects to strings for download
|
| 1301 |
-
d_invoice_date = st.session_state.get(f"Invoice Date_{selected_hash}", None)
|
| 1302 |
-
d_due_date = st.session_state.get(f"Due Date_{selected_hash}", None)
|
| 1303 |
-
|
| 1304 |
-
d_invoice_date_str = ""
|
| 1305 |
-
if d_invoice_date is not None:
|
| 1306 |
-
try:
|
| 1307 |
-
d_invoice_date_str = d_invoice_date.strftime("%d-%b-%Y")
|
| 1308 |
-
except (AttributeError, ValueError):
|
| 1309 |
-
d_invoice_date_str = ""
|
| 1310 |
-
|
| 1311 |
-
d_due_date_str = ""
|
| 1312 |
-
if d_due_date is not None:
|
| 1313 |
-
try:
|
| 1314 |
-
d_due_date_str = d_due_date.strftime("%d-%b-%Y")
|
| 1315 |
-
except (AttributeError, ValueError):
|
| 1316 |
-
d_due_date_str = ""
|
| 1317 |
|
|
|
|
| 1318 |
download_data = {
|
| 1319 |
'Invoice Number': st.session_state.get(f"Invoice Number_{selected_hash}", ''),
|
| 1320 |
-
'Invoice Date':
|
| 1321 |
-
'Due Date':
|
| 1322 |
-
'Currency':
|
| 1323 |
-
'Subtotal':
|
| 1324 |
-
'Tax Percentage':
|
| 1325 |
-
'Total Tax':
|
| 1326 |
-
'Total Amount':
|
| 1327 |
'Sender Name': st.session_state.get(f"Sender Name_{selected_hash}", ''),
|
| 1328 |
'Sender Address': st.session_state.get(f"Sender Address_{selected_hash}", ''),
|
| 1329 |
'Recipient Name': st.session_state.get(f"Recipient Name_{selected_hash}", ''),
|
|
@@ -1337,7 +1352,7 @@ elif len(st.session_state.batch_results) > 0:
|
|
| 1337 |
'bank_routing': st.session_state.get(f"Bank_bank_routing_{selected_hash}", ''),
|
| 1338 |
'bank_branch': st.session_state.get(f"Bank_bank_branch_{selected_hash}", '')
|
| 1339 |
},
|
| 1340 |
-
'Itemized Data':
|
| 1341 |
}
|
| 1342 |
rows = flatten_invoice_to_rows(download_data)
|
| 1343 |
full_df = pd.DataFrame(rows)
|
|
|
|
| 1176 |
"Quantity": it.get("Quantity", it.get("Item Quantity", 0)),
|
| 1177 |
"Unit Price": it.get("Unit Price", it.get("Item Unit Price", 0.0)),
|
| 1178 |
"Amount": it.get("Amount", it.get("Item Amount", 0.0)),
|
| 1179 |
+
"IO Number/Cost Centre": it.get("IO Number/Cost Centre", ""), # New column
|
| 1180 |
"Tax": it.get("Tax", it.get("Item Tax", 0.0)),
|
| 1181 |
"Line Total": it.get("Line Total", it.get("Item Line Total", 0.0)),
|
| 1182 |
})
|
| 1183 |
|
| 1184 |
items_df = pd.DataFrame(normalized) if normalized else pd.DataFrame(
|
| 1185 |
+
columns=["Description", "Quantity", "Unit Price", "Amount", "IO Number/Cost Centre", "Tax", "Line Total"]
|
| 1186 |
)
|
| 1187 |
|
| 1188 |
+
# Configure column widths to make Description wider and avoid horizontal scrolling
|
| 1189 |
+
column_config = {
|
| 1190 |
+
"Description": st.column_config.TextColumn(
|
| 1191 |
+
"Description",
|
| 1192 |
+
width="large", # Make description column wider
|
| 1193 |
+
),
|
| 1194 |
+
"Quantity": st.column_config.NumberColumn(
|
| 1195 |
+
"Quantity",
|
| 1196 |
+
width="small",
|
| 1197 |
+
),
|
| 1198 |
+
"Unit Price": st.column_config.NumberColumn(
|
| 1199 |
+
"Unit Price",
|
| 1200 |
+
width="small",
|
| 1201 |
+
format="%.2f"
|
| 1202 |
+
),
|
| 1203 |
+
"Amount": st.column_config.NumberColumn(
|
| 1204 |
+
"Amount",
|
| 1205 |
+
width="small",
|
| 1206 |
+
format="%.2f"
|
| 1207 |
+
),
|
| 1208 |
+
"IO Number/Cost Centre": st.column_config.TextColumn(
|
| 1209 |
+
"IO Number/Cost Centre",
|
| 1210 |
+
width="medium",
|
| 1211 |
+
),
|
| 1212 |
+
"Tax": st.column_config.NumberColumn(
|
| 1213 |
+
"Tax",
|
| 1214 |
+
width="small",
|
| 1215 |
+
format="%.2f"
|
| 1216 |
+
),
|
| 1217 |
+
"Line Total": st.column_config.NumberColumn(
|
| 1218 |
+
"Line Total",
|
| 1219 |
+
width="small",
|
| 1220 |
+
format="%.2f"
|
| 1221 |
+
),
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
# Show editor without totals - no horizontal scrolling
|
| 1225 |
edited_df = st.data_editor(
|
| 1226 |
items_df,
|
| 1227 |
num_rows="dynamic",
|
| 1228 |
key=f"items_editor_{selected_hash}",
|
| 1229 |
use_container_width=True,
|
| 1230 |
height=DATA_EDITOR_HEIGHT - 50, # Reduce height slightly for totals below
|
| 1231 |
+
column_config=column_config,
|
| 1232 |
)
|
| 1233 |
|
| 1234 |
# Display non-editable totals row immediately below (looks integrated)
|
|
|
|
| 1243 |
"Quantity": "",
|
| 1244 |
"Unit Price": "",
|
| 1245 |
"Amount": f"${total_amount:,.2f}",
|
| 1246 |
+
"IO Number/Cost Centre": "", # Empty for totals row
|
| 1247 |
"Tax": f"${total_tax:,.2f}",
|
| 1248 |
"Line Total": f"${total_line_total:,.2f}"
|
| 1249 |
}])
|
|
|
|
| 1258 |
saved = st.form_submit_button("💾 Save All Edits")
|
| 1259 |
# ----------------- FORM END -----------------
|
| 1260 |
|
| 1261 |
+
# Calculate current values for display and download (always, not just on save)
|
| 1262 |
+
currency = st.session_state.get(f"Currency_{selected_hash}", 'USD')
|
| 1263 |
+
if currency == 'Other':
|
| 1264 |
+
currency = st.session_state.get(f"Currency_Custom_{selected_hash}", '')
|
| 1265 |
|
| 1266 |
+
# Convert date objects to normalized strings (dd-MMM-yyyy format)
|
| 1267 |
+
invoice_date = st.session_state.get(f"Invoice Date_{selected_hash}", None)
|
| 1268 |
+
due_date = st.session_state.get(f"Due Date_{selected_hash}", None)
|
| 1269 |
|
| 1270 |
+
invoice_date_str = ""
|
| 1271 |
+
if invoice_date is not None:
|
| 1272 |
+
try:
|
| 1273 |
+
invoice_date_str = invoice_date.strftime("%d-%b-%Y")
|
| 1274 |
+
except (AttributeError, ValueError):
|
| 1275 |
+
invoice_date_str = ""
|
| 1276 |
|
| 1277 |
+
due_date_str = ""
|
| 1278 |
+
if due_date is not None:
|
| 1279 |
+
try:
|
| 1280 |
+
due_date_str = due_date.strftime("%d-%b-%Y")
|
| 1281 |
+
except (AttributeError, ValueError):
|
| 1282 |
+
due_date_str = ""
|
| 1283 |
|
| 1284 |
+
# Calculate totals from line items (always, for both save and download)
|
| 1285 |
+
line_items_list = edited_df.to_dict('records')
|
| 1286 |
+
calculated_subtotal = sum(clean_float(item.get('Amount', 0)) for item in line_items_list)
|
| 1287 |
+
calculated_total_tax = sum(clean_float(item.get('Tax', 0)) for item in line_items_list)
|
| 1288 |
+
calculated_total = sum(clean_float(item.get('Line Total', 0)) for item in line_items_list)
|
| 1289 |
|
| 1290 |
+
# Calculate tax percentage if possible
|
| 1291 |
+
calculated_tax_pct = 0.0
|
| 1292 |
+
if calculated_subtotal > 0 and calculated_total_tax > 0:
|
| 1293 |
+
calculated_tax_pct = round((calculated_total_tax / calculated_subtotal) * 100, 4)
|
| 1294 |
|
| 1295 |
+
if saved:
|
| 1296 |
+
# Build updated data structure
|
| 1297 |
updated = {
|
| 1298 |
'Invoice Number': st.session_state.get(f"Invoice Number_{selected_hash}", ''),
|
| 1299 |
'Invoice Date': invoice_date_str,
|
|
|
|
| 1323 |
"Address": st.session_state.get(f"Recipient Address_{selected_hash}", '')},
|
| 1324 |
}
|
| 1325 |
|
| 1326 |
+
# Save to batch_results (this persists the data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1327 |
st.session_state.batch_results[selected_hash]["edited_data"] = updated
|
| 1328 |
+
|
| 1329 |
+
# Show success message
|
| 1330 |
st.success(f"✅ Saved: {current['file_name']} | Updated totals: Subtotal=${calculated_subtotal:,.2f}, Tax=${calculated_total_tax:,.2f}, Total=${calculated_total:,.2f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1331 |
|
| 1332 |
+
# Per-file CSV download (ALWAYS visible, uses current edited values)
|
| 1333 |
download_data = {
|
| 1334 |
'Invoice Number': st.session_state.get(f"Invoice Number_{selected_hash}", ''),
|
| 1335 |
+
'Invoice Date': invoice_date_str,
|
| 1336 |
+
'Due Date': due_date_str,
|
| 1337 |
+
'Currency': currency,
|
| 1338 |
+
'Subtotal': calculated_subtotal, # Use calculated value
|
| 1339 |
+
'Tax Percentage': calculated_tax_pct, # Use calculated value
|
| 1340 |
+
'Total Tax': calculated_total_tax, # Use calculated value
|
| 1341 |
+
'Total Amount': calculated_total, # Use calculated value
|
| 1342 |
'Sender Name': st.session_state.get(f"Sender Name_{selected_hash}", ''),
|
| 1343 |
'Sender Address': st.session_state.get(f"Sender Address_{selected_hash}", ''),
|
| 1344 |
'Recipient Name': st.session_state.get(f"Recipient Name_{selected_hash}", ''),
|
|
|
|
| 1352 |
'bank_routing': st.session_state.get(f"Bank_bank_routing_{selected_hash}", ''),
|
| 1353 |
'bank_branch': st.session_state.get(f"Bank_bank_branch_{selected_hash}", '')
|
| 1354 |
},
|
| 1355 |
+
'Itemized Data': line_items_list
|
| 1356 |
}
|
| 1357 |
rows = flatten_invoice_to_rows(download_data)
|
| 1358 |
full_df = pd.DataFrame(rows)
|