crm

package
v1.801.307 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package crm mounts the Hanzo Cloud /v1/crm/* surface: a native-Go, per-org CRM (companies, contacts, opportunities) on Base/SQLite. It is the first slice of the "collapse the business apps into the unified cloud binary" program (universe/docs/architecture/unified-backend-go.md) — a native-Go port of the Twenty CRM core model, NOT a proxy to a NestJS backend.

The three entities are faithful to Twenty's `company` / `person` / `opportunity` standard objects, with Twenty's composite fields (FULL_NAME, EMAILS, CURRENCY, LINKS, ADDRESS) flattened to scalar columns for SQLite.

Tenant isolation is enforced SERVER-SIDE on every request: the org is c.Org() — the value SanitizeIdentity minted from the VALIDATED bearer owner claim (HIP-0026) — and NEVER a client-supplied header. Every store query filters WHERE org=?, so one tenant can never read or mutate another's data.

Surface (all org-scoped; /v1 only):

GET    /v1/crm/summary               per-org row counts (companies/contacts/opps)
GET    /v1/crm/companies             list companies                 -> {data:[…]}
POST   /v1/crm/companies             create a company               -> Company (201)
GET    /v1/crm/companies/:id         company detail                 -> Company
PUT    /v1/crm/companies/:id         update a company               -> Company
DELETE /v1/crm/companies/:id         delete a company (+ clear refs)
GET    /v1/crm/contacts              list contacts (?companyId=)     -> {data:[…]}
POST   /v1/crm/contacts             create a contact               -> Contact (201)
GET    /v1/crm/contacts/:id          contact detail                 -> Contact
PUT    /v1/crm/contacts/:id          update a contact               -> Contact
DELETE /v1/crm/contacts/:id          delete a contact (+ clear refs)
GET    /v1/crm/opportunities         list opportunities (?stage=)    -> {data:[…]}
POST   /v1/crm/opportunities        create an opportunity          -> Opportunity (201)
GET    /v1/crm/opportunities/:id     opportunity detail             -> Opportunity
PUT    /v1/crm/opportunities/:id     update an opportunity          -> Opportunity
DELETE /v1/crm/opportunities/:id     delete an opportunity

Order 131: binds /v1/crm/* before the AI subsystem's /v1/* catch-all (150). serve.go auto-registers GET /v1/crm/health.

Index

Constants

View Source
const (
	StageApplied        = "applied"
	StageScreened       = "screened"
	StageQualified      = "qualified"
	StageCreditsOffered = "credits-offered"
	StageOnboarded      = "onboarded"
	StageRejected       = "rejected"
)

Startup pipeline stages, in order. `rejected` is an off-pipeline terminal.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the crm surface onto app per HIP-0106. Complex flavour: it keeps a package global (mounted) for Shutdown, so it constructs the Service value directly.

func Shutdown

func Shutdown() error

Shutdown closes the crm store. Idempotent.

Types

type Application

type Application struct {
	ID          string         `json:"id"`
	Org         string         `json:"-"`
	Company     string         `json:"company"`
	Website     string         `json:"website"`
	ContactName string         `json:"contactName"`
	Email       string         `json:"email"`
	Role        string         `json:"role"`
	Stage       string         `json:"stage"`
	Tier1       bool           `json:"tier1"`
	Metadata    map[string]any `json:"metadata"`
	Screen      ScreenResult   `json:"screen"`
	Events      []StageEvent   `json:"events"`
	CompanyID   string         `json:"companyId"`
	ContactID   string         `json:"contactId"`
	Reason      string         `json:"reason"`
	CreatedAt   int64          `json:"createdAt"`
	UpdatedAt   int64          `json:"updatedAt"`
}

Application is one startup-program submission plus its AI screen and pipeline state. Metadata carries the FULL submitted payload (all form fields, including arrays like tier1Investors/useCases); the promoted columns are query/display projections. Tier1 is deterministically derived at intake from the submitted fund list (independent of the AI screen's judgement).

type Company

type Company struct {
	ID         string `json:"id"`
	Org        string `json:"-"`
	Name       string `json:"name"`
	DomainName string `json:"domainName"`
	Employees  int64  `json:"employees"`
	City       string `json:"city"`
	Country    string `json:"country"`
	ARR        int64  `json:"arr"`
	Currency   string `json:"currency"`
	ICP        bool   `json:"idealCustomerProfile"`
	Linkedin   string `json:"linkedinLink"`
	XLink      string `json:"xLink"`
	CreatedAt  int64  `json:"createdAt"`
	UpdatedAt  int64  `json:"updatedAt"`
}

Company is an org-scoped account record, faithful to Twenty's `company` standard object (composites flattened to scalar columns for SQLite): ARR is minor units (cents) of Currency; ICP is the ideal-customer-profile flag.

type Contact

type Contact struct {
	ID        string `json:"id"`
	Org       string `json:"-"`
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
	Email     string `json:"email"`
	Phone     string `json:"phone"`
	JobTitle  string `json:"jobTitle"`
	City      string `json:"city"`
	CompanyID string `json:"companyId"`
	Linkedin  string `json:"linkedinLink"`
	XLink     string `json:"xLink"`
	CreatedAt int64  `json:"createdAt"`
	UpdatedAt int64  `json:"updatedAt"`
}

Contact is an org-scoped person record, faithful to Twenty's `person` standard object (FULL_NAME/EMAILS/PHONES composites flattened). CompanyID is an optional in-org relation to a Company.

type Opportunity

type Opportunity struct {
	ID             string `json:"id"`
	Org            string `json:"-"`
	Name           string `json:"name"`
	Amount         int64  `json:"amount"`
	Currency       string `json:"currency"`
	Stage          string `json:"stage"`
	CloseDate      int64  `json:"closeDate"`
	CompanyID      string `json:"companyId"`
	PointOfContact string `json:"pointOfContactId"`
	CreatedAt      int64  `json:"createdAt"`
	UpdatedAt      int64  `json:"updatedAt"`
}

Opportunity is an org-scoped deal record, faithful to Twenty's `opportunity` standard object. Amount is minor units (cents) of Currency; CloseDate is a unix second (0 == unset). Stage is validated against the default pipeline.

type ScreenResult

type ScreenResult struct {
	Status           string `json:"status"` // pending | done | failed
	Score            int    `json:"score"`  // 0..100
	Tier1Backed      string `json:"tier1Backed"`
	SuggestedCredits int    `json:"suggestedCredits"` // 0 | 5000 | 25000 | 50000 | 150000
	Summary          string `json:"summary"`
	DraftReply       string `json:"draftReply"`
	Model            string `json:"model"`
	ScreenedAt       int64  `json:"screenedAt"`
	Error            string `json:"error,omitempty"`
}

ScreenResult is the AI screen stored on an application. Status is pending → done | failed; a failed/absent screen never blocks intake.

type StageEvent

type StageEvent struct {
	From string `json:"from"`
	To   string `json:"to"`
	At   int64  `json:"at"`
	By   string `json:"by"` // "system" or a staff user id
	Note string `json:"note,omitempty"`
}

StageEvent is one entry in an application's append-only stage-transition log.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the CRM database. ONE SQLite file ({DataDir}/crm.db) holds every org's records; tenant isolation is the `org` column, enforced on EVERY query. This mirrors clients/prompts and clients/eval exactly (the ONE storage pattern). MaxOpenConns(1) serializes writes against the single-writer file.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database. Idempotent-safe via sql.DB.

func (*Store) CountApplications

func (s *Store) CountApplications(ctx context.Context, org string) (int, error)

CountApplications returns the org's application count (overview cards).

func (*Store) Counts

func (s *Store) Counts(ctx context.Context, org string) (companies, contacts, opps int, err error)

Counts returns per-org row counts across the three entities — a real, non-fabricated summary for the CRM module's overview cards.

func (*Store) CreateApplication

func (s *Store) CreateApplication(ctx context.Context, a Application) (Application, error)

func (*Store) CreateCompany

func (s *Store) CreateCompany(ctx context.Context, c Company) (Company, error)

func (*Store) CreateContact

func (s *Store) CreateContact(ctx context.Context, c Contact) (Contact, error)

func (*Store) CreateOpportunity

func (s *Store) CreateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)

func (*Store) DeleteCompany

func (s *Store) DeleteCompany(ctx context.Context, org, id string) (bool, error)

DeleteCompany removes a company and NULLs any dangling contact/opportunity refs to it within the same org (no orphaned foreign refs). One transaction.

func (*Store) DeleteContact

func (s *Store) DeleteContact(ctx context.Context, org, id string) (bool, error)

DeleteContact removes a contact and clears any opportunity point-of-contact refs to it within the org. One transaction.

func (*Store) DeleteOpportunity

func (s *Store) DeleteOpportunity(ctx context.Context, org, id string) (bool, error)

func (*Store) FindApplicationByEmailCompany

func (s *Store) FindApplicationByEmailCompany(ctx context.Context, org, email, company string) (Application, error)

FindApplicationByEmailCompany returns the org's application matching a case-insensitive (email, company) pair, or errNotFound. Basis for idempotent intake (a resubmission updates rather than duplicates).

func (*Store) GetApplication

func (s *Store) GetApplication(ctx context.Context, org, id string) (Application, error)

func (*Store) GetCompany

func (s *Store) GetCompany(ctx context.Context, org, id string) (Company, error)

func (*Store) GetContact

func (s *Store) GetContact(ctx context.Context, org, id string) (Contact, error)

func (*Store) GetOpportunity

func (s *Store) GetOpportunity(ctx context.Context, org, id string) (Opportunity, error)

func (*Store) ListApplications

func (s *Store) ListApplications(ctx context.Context, org, stage string, limit int) ([]Application, error)

ListApplications lists an org's applications, optionally filtered by pipeline stage (stage=="" means all). Newest first.

func (*Store) ListCompanies

func (s *Store) ListCompanies(ctx context.Context, org string, limit int) ([]Company, error)

func (*Store) ListContacts

func (s *Store) ListContacts(ctx context.Context, org, companyID string, limit int) ([]Contact, error)

ListContacts lists the org's contacts, optionally filtered to one company (companyID=="" means all). Most-recently-updated first.

func (*Store) ListOpportunities

func (s *Store) ListOpportunities(ctx context.Context, org, stage string, limit int) ([]Opportunity, error)

ListOpportunities lists the org's opportunities, optionally filtered by stage (stage=="" means all). Most-recently-updated first.

func (*Store) UpdateApplication

func (s *Store) UpdateApplication(ctx context.Context, a Application) (Application, error)

UpdateApplication persists the mutable columns (stage, tier1, metadata, screen, events, links, reason). ID/Org/CreatedAt are immutable keys.

func (*Store) UpdateCompany

func (s *Store) UpdateCompany(ctx context.Context, c Company) (Company, error)

func (*Store) UpdateContact

func (s *Store) UpdateContact(ctx context.Context, c Contact) (Contact, error)

func (*Store) UpdateOpportunity

func (s *Store) UpdateOpportunity(ctx context.Context, o Opportunity) (Opportunity, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL