Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 5s
Tests / Test (Go 1.25) (pull_request) Successful in 1m16s
Tests / Test (Go stable) (pull_request) Successful in 1m36s
Promote the generic, reusable parts of the private oo-workspace into the public library/CLI, so oo-workspace can shrink to business glue. - board.go: Board types + (c *Client) SyncBoard — upsert project milestones/tasks by exact title from a YAML board (dry-run when apply=false). CLI: oo projects board-sync. - crm_audit.go: OpportunityAudit + (c *Client) AuditOpportunities — file/task/member counts with a generic class (ok|dup|empty|junk-title). CLI: oo crm audit [--out]. - catalog/names.go: CleanPersonNames / GuessNameFromEmail / FormatProjectTitle (ported from oo-workspace). - catalog: adopt the newer apply/match logic — name repair (never encode company in lastName), company grouping key, contact-info type normalization, preserve an already-applied oo_id. Keeps the local Address/OOProjects fields and the config-driven classifier. Business-only oo-workspace commands (crm clean/migrate, folders, search) deliberately stay private.
64 lines
1.5 KiB
Go
64 lines
1.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func init() {
|
|
crmCmd.AddCommand(crmAuditCmd())
|
|
}
|
|
|
|
func crmAuditCmd() *cobra.Command {
|
|
var outPath string
|
|
cmd := &cobra.Command{
|
|
Use: "audit",
|
|
Short: "Audit opportunities (files/tasks/members per deal) and classify",
|
|
Long: `Lists every opportunity with its file/task/member counts and a coarse class
|
|
(ok | dup | empty | junk-title). --out writes the full JSON audit for later use.`,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
c, err := newOO(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
audits, err := c.AuditOpportunities(cmd.Context())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if outPath != "" {
|
|
b, merr := json.MarshalIndent(audits, "", " ")
|
|
if merr != nil {
|
|
return merr
|
|
}
|
|
if werr := os.WriteFile(outPath, b, 0o644); werr != nil {
|
|
return werr
|
|
}
|
|
}
|
|
byClass := map[string]int{}
|
|
for _, a := range audits {
|
|
byClass[a.Class]++
|
|
}
|
|
classes := make([]string, 0, len(byClass))
|
|
for k := range byClass {
|
|
classes = append(classes, k)
|
|
}
|
|
sort.Strings(classes)
|
|
rows := make([]map[string]any, 0, len(classes))
|
|
for _, k := range classes {
|
|
rows = append(rows, map[string]any{"class": k, "count": byClass[k]})
|
|
}
|
|
printTable([]string{"class", "count"}, rows)
|
|
if outPath != "" {
|
|
fmt.Fprintf(cmd.OutOrStdout(), "wrote %d audits → %s\n", len(audits), outPath)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().StringVar(&outPath, "out", "", "write audit JSON to this file")
|
|
return cmd
|
|
}
|