feat(crm): dedupe duplicates, fix deal titles, and add cleanup CLI

Merge duplicate companies/persons/deals by name, remove duplicate contact
info and opportunity members, and rename malformed " @ Company" titles.
Applications sync now avoids empty-position titles and duplicate members.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-24 12:34:14 +01:00
co-authored by Cursor
parent 1b238aca5a
commit b79c097874
19 changed files with 1782 additions and 31 deletions
+73
View File
@@ -0,0 +1,73 @@
package onlyoffice
import "testing"
func TestNormalizeCompanyName(t *testing.T) {
tests := []struct {
in, want string
}{
{" contoso ", "contoso"},
{"Contoso", "contoso"},
{"Acme Corp", "acme corp"},
}
for _, tc := range tests {
if got := NormalizeCompanyName(tc.in); got != tc.want {
t.Errorf("NormalizeCompanyName(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}
func TestNormalizePersonKey(t *testing.T) {
if got := NormalizePersonKey(" Jane ", " Doe "); got != "jane doe" {
t.Fatalf("got %q", got)
}
}
func TestFixDealTitle(t *testing.T) {
tests := []struct {
in, want string
}{
{" @ contoso", "contoso"},
{"@ contoso", "contoso"},
{"@Acme", "Acme"},
{"Dev@Acme", "Dev @ Acme"},
{"Dev @ Acme", "Dev @ Acme"},
{"Senior Dev @ Acme", "Senior Dev @ Acme"},
{"", ""},
}
for _, tc := range tests {
if got := FixDealTitle(tc.in); got != tc.want {
t.Errorf("FixDealTitle(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}
func TestStripCompanySuffix(t *testing.T) {
if got := StripCompanySuffix("Dev @ Acme"); got != "Dev" {
t.Fatalf("got %q", got)
}
if got := StripCompanySuffix("Dev"); got != "Dev" {
t.Fatalf("got %q", got)
}
}
func TestContactInfoKey(t *testing.T) {
if got := ContactInfoKey("Email", " A@B.COM "); got != "email|a@b.com" {
t.Fatalf("got %q", got)
}
}
func TestMemberDisplayKey(t *testing.T) {
if got := MemberDisplayKey(" contoso "); got != "contoso" {
t.Fatalf("got %q", got)
}
}
func TestDealTitleForApplication(t *testing.T) {
if got := DealTitleForApplication("", "contoso"); got != "contoso" {
t.Fatalf("got %q", got)
}
if got := DealTitleForApplication("Dev", "Acme"); got != "Dev @ Acme" {
t.Fatalf("got %q", got)
}
}