Integrate Data Import with Expenses tab

 FIXES MAJOR WORKFLOW ISSUE:
- Data Import CSV processing now automatically creates expenses
- Etsy fees → 'Transaction Fees' category expenses
- Australia Post shipping → 'Shipping & Postage' expenses
- Prevents duplicate expense creation with reference checking
- Added user notices in both Data Import and Expenses tabs

🔄 INTEGRATION FEATURES:
- Automatic expense creation from CSV order costs
- Duplicate prevention by order number/tracking number
- Proper categorization and tax-deductible flagging
- Clear user notifications and workflow guidance

📊 USER EXPERIENCE:
- Expenses tab now shows integration notice
- Data Import shows automatic expense creation info
- Seamless workflow between sales data and expense tracking
This commit is contained in:
dlawler489 2026-05-05 12:29:07 +10:00
parent f39d4ca266
commit 22799cb732
3 changed files with 169 additions and 1 deletions

View file

@ -5,7 +5,7 @@
**Repository**: https://github.com/dlawler489/etsy-finance-tracker
**Status**: ✅ **LIVE IN PRODUCTION ON MAC MINI**
**Deployment Method**: `docker-compose.deploy.yml` (GitHub Container Registry)
**Last Updated**: May 1, 2026
**Last Updated**: May 1, 2026 - **Modal Bug Fix Deployed**
**Confirmed Working**: ✅ Successfully deployed and operational
### 🏭 **Production Environment Details**
@ -97,6 +97,29 @@ Your Etsy Finance Tracker is now **live and running in production** with all cor
### For Command Line Users
## 🔄 **Latest Updates - Modal Bug Fix Deployment**
### **May 1, 2026 - Critical Modal Fix** ✅ **COMMITTED & BUILDING**
- 🐛 **Issue Fixed**: Modal persistence preventing proper closure after product creation
- ⚡ **Enhancement**: Added duplicate prevention with `isProcessing` state
- 🎛️ **Improvement**: Loading spinner and disabled states during processing
- 🔄 **Workflow**: Enhanced modal closure logic in `DataImport.tsx`
- 🛡️ **Prevention**: Proper state reset on modal close to prevent duplicates
**Deployment Status**:
- ✅ **Code Committed**: Pushed to main branch with commit `f39d4ca`
- 🏗️ **GitHub Action**: Automatically building Docker images in CI/CD pipeline
- 📦 **Container Registry**: New images will be available at `ghcr.io/dlawler489/etsy-finance-tracker:latest`
**To Deploy Latest Fix**:
```bash
# Pull latest code and redeploy
git pull origin main
docker compose -f docker-compose.deploy.yml pull
docker compose -f docker-compose.deploy.yml down
docker compose -f docker-compose.deploy.yml up -d
```
1. **If Docker is available**:
```bash
docker compose -f docker-compose.simple.yml up --build

View file

@ -7,6 +7,7 @@ import { DataManager } from '../utils/dataManager';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../store';
import { addOrder, updateOrder, setOrders } from '../store/slices/orderSlice';
import { addExpenses } from '../store/slices/expenseSlice';
import { Upload, FileText, Package, Truck, Trash2 } from 'lucide-react';
import toast from 'react-hot-toast';
import { dateTestResults } from '../utils/testDateParsing';
@ -110,6 +111,93 @@ export default function DataImport() {
});
};
// Create expenses from CSV import data
const createExpensesFromCsvData = async (
orderCosts: ImportResults['orderCosts'],
shippingRecords: ParsedShippingRecord[]
) => {
try {
// Get existing expenses to avoid duplicates
const existingExpensesRes = await api.get('/expenses?limit=1000');
const existingExpenses = existingExpensesRes.data.expenses || [];
const expensesToCreate: any[] = [];
// Create Etsy fee expenses (check for duplicates by order number)
orderCosts.forEach(order => {
if (order.etsyFees > 0) {
const isDuplicate = existingExpenses.some((expense: any) =>
expense.reference === order.orderNumber &&
expense.vendor === 'Etsy' &&
expense.category === 'Transaction Fees'
);
if (!isDuplicate) {
expensesToCreate.push({
description: `Etsy Fees - Order #${order.orderNumber}`,
amount: order.etsyFees,
category: 'Transaction Fees',
date: order.date,
taxDeductible: true,
vendor: 'Etsy',
reference: order.orderNumber
});
}
}
});
// Create shipping expenses (check for duplicates by tracking number)
shippingRecords.forEach(shipping => {
if (shipping.totalCost > 0) {
const isDuplicate = existingExpenses.some((expense: any) =>
expense.reference === shipping.trackingNumber &&
expense.vendor === 'Australia Post'
);
if (!isDuplicate) {
expensesToCreate.push({
description: `Australia Post - ${shipping.trackingNumber}`,
amount: shipping.totalCost,
category: 'Shipping & Postage',
date: shipping.date,
taxDeductible: true,
vendor: 'Australia Post',
reference: shipping.trackingNumber
});
}
}
});
// Save expenses to database
if (expensesToCreate.length > 0) {
const savedExpenses = [];
let created = 0;
for (const expense of expensesToCreate) {
try {
const res = await api.post('/expenses', expense);
savedExpenses.push(res.data);
created++;
} catch (error) {
console.error('Failed to create expense:', expense, error);
}
}
if (savedExpenses.length > 0) {
dispatch(addExpenses(savedExpenses));
toast.success(`Created ${created} expenses from CSV data (Etsy fees + shipping costs)`);
} else if (expensesToCreate.length === 0) {
toast.success('All expenses from CSV data already exist - no duplicates created');
}
} else {
toast.success('No new expenses to create from CSV data');
}
} catch (error) {
console.error('Error creating expenses:', error);
toast.error('Failed to create expenses from CSV data');
}
};
const processCsvFiles = async () => {
if (!etsyFile) {
setError('Please select an Etsy statement CSV file');
@ -179,6 +267,10 @@ export default function DataImport() {
// Reload orders from API after bulk upsert
const ordersRes = await api.get('/orders?limit=1000');
dispatch(setOrders(ordersRes.data.orders));
// Create expenses from CSV data
await createExpensesFromCsvData(orderCosts, shippingRecords);
toast.success(`CSV imported! Created ${res.data.created} new orders and updated ${res.data.updated} existing orders.`);
} catch {
toast.error('Failed to save orders to database');
@ -658,6 +750,34 @@ export default function DataImport() {
</div>
</div>
{/* Automatic Expense Creation Notice */}
<div className="mt-6 bg-green-50 border border-green-200 rounded-lg p-4">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-green-800">
Automatic Expense Creation
</h3>
<div className="mt-2 text-sm text-green-700">
<p>
When you process CSV files, expenses will be automatically created in the <strong>Expenses tab</strong>:
</p>
<ul className="mt-1 list-disc list-inside">
<li><strong>Etsy fees</strong> from each order (Transaction Fees category)</li>
<li><strong>Australia Post shipping costs</strong> (Shipping & Postage category)</li>
</ul>
<p className="mt-2 text-xs">
This ensures your profit analysis matches your expense tracking automatically!
</p>
</div>
</div>
</div>
</div>
<div className="mt-6 flex gap-4">
<button
onClick={processCsvFiles}

View file

@ -373,6 +373,31 @@ const Expenses = () => {
</div>
</div>
{/* Data Import Integration Notice */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-blue-800">
💡 Automatic Expense Creation
</h3>
<div className="mt-2 text-sm text-blue-700">
<p>
<strong>Etsy fees</strong> and <strong>Australia Post shipping costs</strong> are automatically created as expenses when you process CSV files in the{' '}
<a href="/data-import" className="font-semibold underline hover:text-blue-900">Data Import</a> section.
</p>
<p className="mt-1 text-xs">
Manual CSV imports here are for additional expenses not captured in your sales data.
</p>
</div>
</div>
</div>
</div>
{/* Summary Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<div className="bg-white rounded-lg shadow p-6">