Properly implement transaction deduplication
Using my superior HUMAN brain Idiot ram stick
This commit is contained in:
@@ -5,7 +5,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
|
import { parseTransactionLine, formatISK, PastedTransaction } from '@/utils/priceUtils';
|
||||||
import { IndTransactionRecordNoId, IndJobStatusOptions } from '@/lib/pbtypes';
|
import { IndTransactionRecordNoId, IndJobStatusOptions } from '@/lib/pbtypes';
|
||||||
import { IndJob } from '@/lib/types';
|
import { IndJob } from '@/lib/types';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
@@ -16,14 +16,9 @@ interface BatchTransactionFormProps {
|
|||||||
jobs: IndJob[];
|
jobs: IndJob[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParsedTransaction extends IndTransactionRecordNoId {
|
|
||||||
assignedJobId?: string;
|
|
||||||
isDuplicate?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TransactionGroup {
|
interface TransactionGroup {
|
||||||
itemName: string;
|
itemName: string;
|
||||||
transactions: ParsedTransaction[];
|
transactions: PastedTransaction[];
|
||||||
totalQuantity: number;
|
totalQuantity: number;
|
||||||
totalValue: number;
|
totalValue: number;
|
||||||
}
|
}
|
||||||
@@ -59,16 +54,16 @@ const BatchTransactionForm: React.FC<BatchTransactionFormProps> = ({ onClose, on
|
|||||||
return dateStr.replace('T', ' ');
|
return dateStr.replace('T', ' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTransactionKey = (parsed: ReturnType<typeof parseTransactionLine>): string => {
|
const createTransactionKey = (parsed: PastedTransaction): string => {
|
||||||
if (!parsed) return '';
|
if (!parsed) return '';
|
||||||
const key = [
|
const key = [
|
||||||
normalizeDate(parsed.date.toISOString()),
|
normalizeDate(parsed.date.toString()),
|
||||||
parsed.itemName,
|
parsed.itemName,
|
||||||
parsed.quantity.toString(),
|
parsed.quantity.toString(),
|
||||||
parsed.totalAmount.toString(),
|
parsed.totalPrice.toString(),
|
||||||
parsed.buyer,
|
parsed.buyer,
|
||||||
parsed.location
|
parsed.location
|
||||||
].join('|');
|
].join('|');
|
||||||
return key;
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,33 +82,25 @@ const BatchTransactionForm: React.FC<BatchTransactionFormProps> = ({ onClose, on
|
|||||||
const handlePaste = (value: string) => {
|
const handlePaste = (value: string) => {
|
||||||
setPastedData(value);
|
setPastedData(value);
|
||||||
const lines = value.trim().split('\n');
|
const lines = value.trim().split('\n');
|
||||||
const pasteTransactionMap = new Map<string, ParsedTransaction>();
|
const pasteTransactionMap = new Map<string, PastedTransaction>();
|
||||||
|
|
||||||
// STEP 1: First combine all identical transactions within the pasted data
|
// STEP 1: First combine all identical transactions within the pasted data
|
||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
const parsed = parseTransactionLine(line);
|
const parsed: PastedTransaction | null = parseTransactionLine(line);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
const transactionKey = createTransactionKey(parsed);
|
const transactionKey: string = createTransactionKey(parsed);
|
||||||
|
|
||||||
if (pasteTransactionMap.has(transactionKey)) {
|
if (pasteTransactionMap.has(transactionKey)) {
|
||||||
// Merge with existing transaction in paste
|
// Merge with existing transaction in paste
|
||||||
const existing = pasteTransactionMap.get(transactionKey)!;
|
const existing = pasteTransactionMap.get(transactionKey)!;
|
||||||
existing.quantity += parsed.quantity;
|
existing.quantity += parsed.quantity;
|
||||||
existing.totalPrice += Math.abs(parsed.totalAmount);
|
existing.totalPrice += Math.abs(parsed.totalPrice);
|
||||||
|
const newKey = createTransactionKey(existing);
|
||||||
|
pasteTransactionMap.set(newKey, existing);
|
||||||
|
pasteTransactionMap.delete(transactionKey); // Remove old key
|
||||||
} else {
|
} else {
|
||||||
// Add new transaction
|
// Add new transaction
|
||||||
const newTransaction: ParsedTransaction = {
|
pasteTransactionMap.set(transactionKey, parsed);
|
||||||
date: parsed.date.toISOString(),
|
|
||||||
quantity: parsed.quantity,
|
|
||||||
itemName: parsed.itemName,
|
|
||||||
unitPrice: parsed.unitPrice,
|
|
||||||
totalPrice: Math.abs(parsed.totalAmount),
|
|
||||||
buyer: parsed.buyer,
|
|
||||||
location: parsed.location,
|
|
||||||
corporation: parsed.corporation,
|
|
||||||
wallet: parsed.wallet
|
|
||||||
};
|
|
||||||
pasteTransactionMap.set(transactionKey, newTransaction);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -124,6 +111,7 @@ const BatchTransactionForm: React.FC<BatchTransactionFormProps> = ({ onClose, on
|
|||||||
const matchingJobId = findMatchingJob(transaction.itemName);
|
const matchingJobId = findMatchingJob(transaction.itemName);
|
||||||
if (matchingJobId) {
|
if (matchingJobId) {
|
||||||
relevantJobIds.add(matchingJobId);
|
relevantJobIds.add(matchingJobId);
|
||||||
|
transaction.assignedJobId = matchingJobId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,11 +131,11 @@ const BatchTransactionForm: React.FC<BatchTransactionFormProps> = ({ onClose, on
|
|||||||
pasteTransactionMap.forEach((transaction, key) => {
|
pasteTransactionMap.forEach((transaction, key) => {
|
||||||
const isDuplicate = existingTransactionKeys.has(key);
|
const isDuplicate = existingTransactionKeys.has(key);
|
||||||
transaction.isDuplicate = isDuplicate;
|
transaction.isDuplicate = isDuplicate;
|
||||||
|
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
duplicates++;
|
duplicates++;
|
||||||
transaction.assignedJobId = undefined;
|
transaction.assignedJobId = undefined;
|
||||||
} else {
|
} else if (!!transaction.assignedJobId) {
|
||||||
transaction.assignedJobId = findMatchingJob(transaction.itemName);
|
transaction.assignedJobId = findMatchingJob(transaction.itemName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -344,4 +332,4 @@ const BatchTransactionForm: React.FC<BatchTransactionFormProps> = ({ onClose, on
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BatchTransactionForm;
|
export default BatchTransactionForm;
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { IndTransactionRecordNoId } from "@/lib/pbtypes";
|
||||||
|
|
||||||
export const parseISKAmount = (iskString: string): number => {
|
export const parseISKAmount = (iskString: string): number => {
|
||||||
// Remove "ISK" and any extra whitespace
|
// Remove "ISK" and any extra whitespace
|
||||||
@@ -26,17 +27,12 @@ export const formatISK = (amount: number): string => {
|
|||||||
return `${sign}${formatted} ISK`;
|
return `${sign}${formatted} ISK`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseTransactionLine = (line: string): {
|
export type PastedTransaction = IndTransactionRecordNoId & {
|
||||||
date: Date;
|
assignedJobId?: string;
|
||||||
quantity: number;
|
isDuplicate?: boolean;
|
||||||
itemName: string;
|
}
|
||||||
unitPrice: number;
|
|
||||||
totalAmount: number;
|
export const parseTransactionLine = (line: string): PastedTransaction | null => {
|
||||||
buyer?: string;
|
|
||||||
location?: string;
|
|
||||||
corporation?: string;
|
|
||||||
wallet?: string;
|
|
||||||
} | null => {
|
|
||||||
try {
|
try {
|
||||||
const parts = line.split('\t');
|
const parts = line.split('\t');
|
||||||
if (parts.length < 6) return null;
|
if (parts.length < 6) return null;
|
||||||
@@ -60,14 +56,14 @@ export const parseTransactionLine = (line: string): {
|
|||||||
|
|
||||||
// Parse prices
|
// Parse prices
|
||||||
const unitPrice = parseISKAmount(unitPriceStr);
|
const unitPrice = parseISKAmount(unitPriceStr);
|
||||||
const totalAmount = parseISKAmount(totalAmountStr);
|
const totalPrice = parseISKAmount(totalAmountStr);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date,
|
date: date.toISOString(),
|
||||||
quantity,
|
quantity,
|
||||||
itemName,
|
itemName,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
totalAmount,
|
totalPrice,
|
||||||
buyer,
|
buyer,
|
||||||
location,
|
location,
|
||||||
corporation,
|
corporation,
|
||||||
|
Reference in New Issue
Block a user