Properly implement transaction deduplication

Using my superior HUMAN brain
Idiot ram stick
This commit is contained in:
2025-07-08 21:38:03 +02:00
parent 74dbaab169
commit 3820522f32
2 changed files with 28 additions and 44 deletions

View File

@@ -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;

View File

@@ -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,