Task 5.23: Deduplication / Merge — ContactMergeHistory model, dedup service, API routes (duplicates/merge/merge-history), DedupDialog frontend, i18n, 5 tests
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Deduplication / merge hooks for contacts (Task 5.23).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiPost, apiGet } from './client';
|
||||
|
||||
export interface DuplicateContactBrief {
|
||||
id: string;
|
||||
type: string;
|
||||
displayname: string;
|
||||
name: string | null;
|
||||
firstname: string | null;
|
||||
surname: string | null;
|
||||
email_1: string | null;
|
||||
email_2: string | null;
|
||||
phone_1: string | null;
|
||||
phone_2: string | null;
|
||||
mailing_city: string | null;
|
||||
mailing_postalcode: string | null;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface DuplicatePair {
|
||||
source_contact: DuplicateContactBrief;
|
||||
target_contact: DuplicateContactBrief;
|
||||
similarity_score: number;
|
||||
match_reasons: string[];
|
||||
}
|
||||
|
||||
export interface MergeHistoryItem {
|
||||
id: string;
|
||||
source_contact_id: string;
|
||||
target_contact_id: string;
|
||||
merged_fields: Record<string, unknown>;
|
||||
merged_by: string | null;
|
||||
note: string | null;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface MergeHistoryResponse {
|
||||
items: MergeHistoryItem[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export interface MergeRequest {
|
||||
source_contact_id: string;
|
||||
target_contact_id: string;
|
||||
field_overrides?: Record<string, unknown>;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export function useFindDuplicates() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (params: { threshold?: number; limit?: number }) =>
|
||||
apiPost<DuplicatePair[]>('/contacts/duplicates', {
|
||||
threshold: params.threshold ?? 0.7,
|
||||
limit: params.limit ?? 50,
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['duplicates'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useMergeContacts() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: MergeRequest) =>
|
||||
apiPost('/contacts/merge', data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['contacts'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['duplicates'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mergeHistory'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useMergeHistory(page = 1, pageSize = 20) {
|
||||
return useQuery({
|
||||
queryKey: ['mergeHistory', page, pageSize],
|
||||
queryFn: () =>
|
||||
apiGet<MergeHistoryResponse>(`/contacts/merge-history?page=${page}&page_size=${pageSize}`),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user