thread-master/backend/internal/model/crm_contact/usecase/usecase.go

177 lines
5.3 KiB
Go
Raw Permalink Normal View History

2026-06-27 16:03:28 +00:00
package usecase
import (
"context"
"strings"
"haixun-backend/internal/library/clock"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/model/crm_contact/domain/entity"
domrepo "haixun-backend/internal/model/crm_contact/domain/repository"
domusecase "haixun-backend/internal/model/crm_contact/domain/usecase"
"github.com/google/uuid"
)
type crmContactUseCase struct {
repo domrepo.Repository
}
func NewUseCase(repo domrepo.Repository) domusecase.UseCase {
return &crmContactUseCase{repo: repo}
}
func (u *crmContactUseCase) Create(ctx context.Context, tenantID, ownerUID, brandID, authorID, authorName string) (*domusecase.CRMContactSummary, error) {
if err := requireActor(tenantID, ownerUID); err != nil {
return nil, err
}
authorID = strings.TrimSpace(authorID)
if authorID == "" {
return nil, app.For(code.Persona).InputMissingRequired("author_id is required")
}
authorName = strings.TrimSpace(authorName)
if authorName == "" {
return nil, app.For(code.Persona).InputMissingRequired("author_name is required")
}
now := clock.NowUnixNano()
contact := &entity.CRMContact{
ID: uuid.NewString(),
TenantID: tenantID,
OwnerUID: ownerUID,
BrandID: strings.TrimSpace(brandID),
AuthorID: authorID,
AuthorName: authorName,
Status: entity.ContactStatusLead,
CreateAt: now,
UpdateAt: now,
}
if err := u.repo.Create(ctx, contact); err != nil {
return nil, err
}
summary := toSummary(*contact)
return &summary, nil
}
func (u *crmContactUseCase) Get(ctx context.Context, tenantID, ownerUID, contactID string) (*domusecase.CRMContactSummary, error) {
if err := requireActor(tenantID, ownerUID); err != nil {
return nil, err
}
contactID = strings.TrimSpace(contactID)
if contactID == "" {
return nil, app.For(code.Persona).InputMissingRequired("contact_id is required")
}
item, err := u.repo.Get(ctx, tenantID, ownerUID, contactID)
if err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
func (u *crmContactUseCase) Update(ctx context.Context, req domusecase.UpdateRequest) (*domusecase.CRMContactSummary, error) {
if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
return nil, err
}
contactID := strings.TrimSpace(req.ContactID)
if contactID == "" {
return nil, app.For(code.Persona).InputMissingRequired("contact_id is required")
}
patch := map[string]interface{}{}
if req.Notes != nil {
patch["notes"] = strings.TrimSpace(*req.Notes)
}
if req.Tags != nil {
patch["tags"] = *req.Tags
}
if req.Status != nil {
status := strings.TrimSpace(*req.Status)
if status != "" && status != entity.ContactStatusLead && status != entity.ContactStatusContacted && status != entity.ContactStatusResponded && status != entity.ContactStatusConverted && status != entity.ContactStatusArchived {
return nil, app.For(code.Persona).InputInvalidFormat("invalid contact status")
}
if status != "" {
patch["status"] = status
}
}
if len(patch) == 0 {
return nil, app.For(code.Persona).InputMissingRequired("no fields to update")
}
patch["update_at"] = clock.NowUnixNano()
item, err := u.repo.Update(ctx, req.TenantID, req.OwnerUID, contactID, patch)
if err != nil {
return nil, err
}
summary := toSummary(*item)
return &summary, nil
}
func (u *crmContactUseCase) Delete(ctx context.Context, tenantID, ownerUID, contactID string) error {
if err := requireActor(tenantID, ownerUID); err != nil {
return err
}
contactID = strings.TrimSpace(contactID)
if contactID == "" {
return app.For(code.Persona).InputMissingRequired("contact_id is required")
}
return u.repo.Delete(ctx, tenantID, ownerUID, contactID)
}
func (u *crmContactUseCase) List(ctx context.Context, req domusecase.ListRequest) ([]domusecase.CRMContactSummary, int, error) {
if err := requireActor(req.TenantID, req.OwnerUID); err != nil {
return nil, 0, err
}
page := req.Page
pageSize := req.PageSize
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 100 {
pageSize = 100
}
filter := domrepo.ListFilter{
BrandID: strings.TrimSpace(req.BrandID),
Status: strings.TrimSpace(req.Status),
Tag: strings.TrimSpace(req.Tag),
Limit: pageSize,
Offset: (page - 1) * pageSize,
}
items, total, err := u.repo.List(ctx, req.TenantID, req.OwnerUID, filter)
if err != nil {
return nil, 0, err
}
out := make([]domusecase.CRMContactSummary, 0, len(items))
for _, item := range items {
out = append(out, toSummary(item))
}
return out, total, nil
}
func requireActor(tenantID, ownerUID string) error {
if strings.TrimSpace(tenantID) == "" || strings.TrimSpace(ownerUID) == "" {
return app.For(code.Auth).AuthUnauthorized("missing actor")
}
return nil
}
func toSummary(item entity.CRMContact) domusecase.CRMContactSummary {
return domusecase.CRMContactSummary{
ID: item.ID,
BrandID: item.BrandID,
AuthorID: item.AuthorID,
AuthorName: item.AuthorName,
AuthorAvatar: item.AuthorAvatar,
AuthorFollowers: item.AuthorFollowers,
ScanPostID: item.ScanPostID,
Notes: item.Notes,
Tags: item.Tags,
Status: item.Status,
OutreachCount: item.OutreachCount,
LastContactedAt: item.LastContactedAt,
CreateAt: item.CreateAt,
UpdateAt: item.UpdateAt,
}
}