prepare($sql); $stmt->execute($params); $matches = $stmt->fetchAll(); foreach ($matches as $m) { $decName = Encryption::decrypt($m['supplier_name']); $duplicates[] = [ 'id' => $m['id'], 'invoice_number' => $m['invoice_number'], 'invoice_date' => $m['invoice_date'], 'grand_total' => $m['grand_total'], 'status' => $m['status'], 'supplier_name' => ($decName !== false && $decName !== null) ? $decName : $m['supplier_name'], 'match_type' => 'exact_number', 'confidence' => 100, ]; } } // 2. Fuzzy match: same supplier TIN + same total + same date if ($supplierTin && $grandTotal && $invoiceDate && empty($duplicates)) { $sql = "SELECT id, invoice_number, invoice_date, grand_total, status, supplier_name, supplier_tin FROM invoices WHERE tenant_id = ? AND invoice_date = ? AND ABS(grand_total - ?) < 0.01"; $params = [$tenantId, $invoiceDate, $grandTotal]; if ($excludeId) { $sql .= " AND id != ?"; $params[] = $excludeId; } $stmt = $db->prepare($sql); $stmt->execute($params); $matches = $stmt->fetchAll(); foreach ($matches as $m) { $decTin = Encryption::decrypt($m['supplier_tin']); $decName = Encryption::decrypt($m['supplier_name']); if ($decTin === $supplierTin || $m['supplier_tin'] === $supplierTin) { $duplicates[] = [ 'id' => $m['id'], 'invoice_number' => $m['invoice_number'], 'invoice_date' => $m['invoice_date'], 'grand_total' => $m['grand_total'], 'status' => $m['status'], 'supplier_name' => ($decName !== false && $decName !== null) ? $decName : $m['supplier_name'], 'match_type' => 'fuzzy_tin_total_date', 'confidence' => 90, ]; } } } // 3. Near match: same total + near date (±3 days) if ($grandTotal && $invoiceDate && empty($duplicates)) { $sql = "SELECT id, invoice_number, invoice_date, grand_total, status, supplier_name FROM invoices WHERE tenant_id = ? AND ABS(grand_total - ?) < 0.01 AND ABS(DATEDIFF(invoice_date, ?)) <= 3"; $params = [$tenantId, $grandTotal, $invoiceDate]; if ($excludeId) { $sql .= " AND id != ?"; $params[] = $excludeId; } $sql .= " LIMIT 5"; $stmt = $db->prepare($sql); $stmt->execute($params); $matches = $stmt->fetchAll(); foreach ($matches as $m) { $decName = Encryption::decrypt($m['supplier_name']); $duplicates[] = [ 'id' => $m['id'], 'invoice_number' => $m['invoice_number'], 'invoice_date' => $m['invoice_date'], 'grand_total' => $m['grand_total'], 'status' => $m['status'], 'supplier_name' => ($decName !== false && $decName !== null) ? $decName : $m['supplier_name'], 'match_type' => 'near_total_date', 'confidence' => 60, ]; } } json_success([ 'is_duplicate' => !empty($duplicates), 'matches' => $duplicates, 'count' => count($duplicates), ], empty($duplicates) ? 'لا توجد فواتير مكررة' : 'تم العثور على فواتير مشابهة');