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 e03fd62200
commit c77519fac5
21 changed files with 1832 additions and 46 deletions
+52
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strconv"
onlyoffice "github.com/eslider/go-onlyoffice"
"github.com/spf13/cobra"
)
@@ -21,6 +22,9 @@ func init() {
opportunitiesCmd.AddCommand(oppDeleteCmd())
opportunitiesCmd.AddCommand(oppStagesCmd())
opportunitiesCmd.AddCommand(oppMemberAddCmd())
opportunitiesCmd.AddCommand(oppDedupeCmd())
opportunitiesCmd.AddCommand(oppDedupeMembersCmd())
opportunitiesCmd.AddCommand(oppFixTitlesCmd())
}
func oppListCmd() *cobra.Command {
@@ -175,3 +179,51 @@ func oppMemberAddCmd() *cobra.Command {
},
}
}
func oppDedupeCmd() *cobra.Command {
var ignoreCompanySuffix bool
cmd := &cobra.Command{
Use: "dedupe",
Short: "Merge duplicate opportunities by title",
RunE: dedupeRunE(func(cmd *cobra.Command, c *onlyoffice.Client) error {
res, err := onlyoffice.DedupeOpportunities(cmd.Context(), c, ignoreCompanySuffix)
if err != nil {
return err
}
printObject(res)
return nil
}),
}
cmd.Flags().BoolVar(&ignoreCompanySuffix, "ignore-company-suffix", false, "group by position only")
return cmd
}
func oppDedupeMembersCmd() *cobra.Command {
return &cobra.Command{
Use: "dedupe-members",
Short: "Remove duplicate contacts on each deal (by id and display name)",
RunE: dedupeRunE(func(cmd *cobra.Command, c *onlyoffice.Client) error {
res, err := onlyoffice.DedupeOpportunityMembers(cmd.Context(), c)
if err != nil {
return err
}
printObject(res)
return nil
}),
}
}
func oppFixTitlesCmd() *cobra.Command {
return &cobra.Command{
Use: "fix-titles",
Short: "Fix malformed deal titles (leading @, spacing)",
RunE: dedupeRunE(func(cmd *cobra.Command, c *onlyoffice.Client) error {
res, err := onlyoffice.FixOpportunityTitles(cmd.Context(), c)
if err != nil {
return err
}
printObject(res)
return nil
}),
}
}