abbe7a18fc
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner - P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var - P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup - P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns - P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs) - P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import - P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default - P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed - P2: 28 frontend TODOs (hardcoded constants, deprecated notification API) - P3: dead code, duplicates, deprecated imports, private attr, __import__ inline - P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n) - ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix) - F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String) - Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
270 lines
9.0 KiB
TypeScript
270 lines
9.0 KiB
TypeScript
// TODO: P2-F9 — Consolidate dual API clients
|
|
/**
|
|
* Unified Contact (Rentman-style) hooks and standalone API functions.
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { apiGet, apiPost, apiPut, apiDelete } from './client';
|
|
import { PaginatedResponse } from './types';
|
|
|
|
export interface ContactPerson {
|
|
id: string;
|
|
contact_id: string;
|
|
displayname: string;
|
|
firstname?: string | null;
|
|
middle_name?: string | null;
|
|
lastname?: string | null;
|
|
function?: string | null;
|
|
phone?: string | null;
|
|
mobilephone?: string | null;
|
|
email?: string | null;
|
|
street?: string | null;
|
|
number?: string | null;
|
|
postalcode?: string | null;
|
|
city?: string | null;
|
|
state?: string | null;
|
|
country?: string | null;
|
|
tags?: string | null;
|
|
custom?: Record<string, any> | null;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface UnifiedContact {
|
|
id: string;
|
|
type: 'company' | 'person';
|
|
displayname: string;
|
|
name?: string | null;
|
|
firstname?: string | null;
|
|
surname?: string | null;
|
|
suffix?: string | null;
|
|
ext_name_line?: string | null;
|
|
gender?: string | null;
|
|
code?: string | null;
|
|
accounting_code?: string | null;
|
|
vendor_accounting_code?: string | null;
|
|
// Mailing address
|
|
mailing_street?: string | null;
|
|
mailing_number?: string | null;
|
|
mailing_unit_number?: string | null;
|
|
mailing_district?: string | null;
|
|
mailing_extra_address_line?: string | null;
|
|
mailing_postalcode?: string | null;
|
|
mailing_city?: string | null;
|
|
mailing_state?: string | null;
|
|
mailing_country?: string | null;
|
|
// Visit address
|
|
visit_street?: string | null;
|
|
visit_number?: string | null;
|
|
visit_unit_number?: string | null;
|
|
visit_district?: string | null;
|
|
visit_extra_address_line?: string | null;
|
|
visit_postalcode?: string | null;
|
|
visit_city?: string | null;
|
|
visit_state?: string | null;
|
|
// Invoice address
|
|
invoice_street?: string | null;
|
|
invoice_number?: string | null;
|
|
invoice_unit_number?: string | null;
|
|
invoice_district?: string | null;
|
|
invoice_extra_address_line?: string | null;
|
|
invoice_postalcode?: string | null;
|
|
invoice_city?: string | null;
|
|
invoice_state?: string | null;
|
|
invoice_country?: string | null;
|
|
country?: string | null;
|
|
// Communication
|
|
phone_1?: string | null;
|
|
phone_2?: string | null;
|
|
email_1?: string | null;
|
|
email_2?: string | null;
|
|
website?: string | null;
|
|
// Financial
|
|
vat_code?: string | null;
|
|
fiscal_code?: string | null;
|
|
commerce_code?: string | null;
|
|
purchase_number?: string | null;
|
|
bic?: string | null;
|
|
bank_account?: string | null;
|
|
// Discounts
|
|
discount_crew: number;
|
|
discount_transport: number;
|
|
discount_rental: number;
|
|
discount_sale: number;
|
|
discount_subrent: number;
|
|
discount_total: number;
|
|
// Geo
|
|
latitude?: number | null;
|
|
longitude?: number | null;
|
|
// Notes
|
|
projectnote?: string | null;
|
|
projectnote_title?: string | null;
|
|
contact_warning?: string | null;
|
|
tags?: string | null;
|
|
image?: string | null;
|
|
custom?: Record<string, any> | null;
|
|
folder_id?: string | null;
|
|
default_person_id?: string | null;
|
|
admin_contactperson_id?: string | null;
|
|
contact_persons?: ContactPerson[];
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export function useUnifiedContacts(
|
|
page = 1,
|
|
pageSize = 25,
|
|
search?: string,
|
|
contactType?: string,
|
|
sortBy?: string,
|
|
sortOrder?: string,
|
|
folderId?: string,
|
|
) {
|
|
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
|
if (search) params.set('search', search);
|
|
if (contactType) params.set('type', contactType);
|
|
if (sortBy) params.set('sort_by', sortBy);
|
|
if (sortOrder) params.set('sort_order', sortOrder);
|
|
if (folderId) params.set('folder_id', folderId);
|
|
return useQuery({
|
|
queryKey: ['unifiedContacts', page, pageSize, search, contactType, sortBy, sortOrder, folderId],
|
|
queryFn: () =>
|
|
apiGet<PaginatedResponse<UnifiedContact>>(`/contacts?${params.toString()}`),
|
|
});
|
|
}
|
|
|
|
export function useUnifiedContact(id?: string) {
|
|
return useQuery({
|
|
queryKey: ['unifiedContacts', id],
|
|
queryFn: () => apiGet<UnifiedContact & { contact_persons: ContactPerson[] }>(`/contacts/${id}`),
|
|
enabled: !!id,
|
|
});
|
|
}
|
|
|
|
export function useCreateUnifiedContact() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: Partial<UnifiedContact>) => apiPost('/contacts', data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateUnifiedContact() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: string; data: Partial<UnifiedContact> }) =>
|
|
apiPut(`/contacts/${id}`, data),
|
|
onSuccess: (_data, variables) => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.id] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteUnifiedContact() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, hard }: { id: string; hard?: boolean }) =>
|
|
apiDelete(`/contacts/${id}${hard ? '?hard=true' : ''}`),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useContactPersons(contactId?: string) {
|
|
return useQuery({
|
|
queryKey: ['unifiedContacts', contactId, 'persons'],
|
|
queryFn: () => apiGet<{ items: ContactPerson[] }>(`/contacts/${contactId}/persons`),
|
|
enabled: !!contactId,
|
|
});
|
|
}
|
|
|
|
export function useCreateContactPerson() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ contactId, data }: { contactId: string; data: Partial<ContactPerson> }) =>
|
|
apiPost(`/contacts/${contactId}/persons`, data),
|
|
onSuccess: (_data, variables) => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId] });
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId, 'persons'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateContactPerson() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ contactId, personId, data }: { contactId: string; personId: string; data: Partial<ContactPerson> }) =>
|
|
apiPut(`/contacts/${contactId}/persons/${personId}`, data),
|
|
onSuccess: (_data, variables) => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId] });
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId, 'persons'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteContactPerson() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ contactId, personId }: { contactId: string; personId: string }) =>
|
|
apiDelete(`/contacts/${contactId}/persons/${personId}`),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
|
},
|
|
});
|
|
}
|
|
|
|
// Standalone API functions (for non-hook usage)
|
|
export async function fetchContacts(
|
|
page = 1,
|
|
pageSize = 25,
|
|
search?: string,
|
|
contactType?: string,
|
|
sortBy?: string,
|
|
sortOrder?: string,
|
|
): Promise<PaginatedResponse<UnifiedContact>> {
|
|
const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) });
|
|
if (search) params.set('search', search);
|
|
if (contactType) params.set('type', contactType);
|
|
if (sortBy) params.set('sort_by', sortBy);
|
|
if (sortOrder) params.set('sort_order', sortOrder);
|
|
return apiGet<PaginatedResponse<UnifiedContact>>(`/contacts?${params.toString()}`);
|
|
}
|
|
|
|
export async function fetchContact(id: string): Promise<UnifiedContact & { contact_persons: ContactPerson[] }> {
|
|
return apiGet<UnifiedContact & { contact_persons: ContactPerson[] }>(`/contacts/${id}`);
|
|
}
|
|
|
|
export async function createContact(data: Partial<UnifiedContact>): Promise<UnifiedContact> {
|
|
return apiPost<UnifiedContact>('/contacts', data);
|
|
}
|
|
|
|
export async function updateContact(id: string, data: Partial<UnifiedContact>): Promise<UnifiedContact> {
|
|
return apiPut<UnifiedContact>(`/contacts/${id}`, data);
|
|
}
|
|
|
|
export async function deleteContact(id: string, hard?: boolean): Promise<void> {
|
|
await apiDelete(`/contacts/${id}${hard ? '?hard=true' : ''}`);
|
|
}
|
|
|
|
export async function fetchContactPersons(contactId: string): Promise<{ items: ContactPerson[] }> {
|
|
return apiGet<{ items: ContactPerson[] }>(`/contacts/${contactId}/persons`);
|
|
}
|
|
|
|
export async function createContactPerson(contactId: string, data: Partial<ContactPerson>): Promise<ContactPerson> {
|
|
return apiPost<ContactPerson>(`/contacts/${contactId}/persons`, data);
|
|
}
|
|
|
|
export async function updateContactPerson(contactId: string, personId: string, data: Partial<ContactPerson>): Promise<ContactPerson> {
|
|
return apiPut<ContactPerson>(`/contacts/${contactId}/persons/${personId}`, data);
|
|
}
|
|
|
|
export async function deleteContactPerson(contactId: string, personId: string): Promise<void> {
|
|
await apiDelete(`/contacts/${contactId}/persons/${personId}`);
|
|
}
|