177 lines
5.3 KiB
Go
177 lines
5.3 KiB
Go
|
|
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,
|
||
|
|
}
|
||
|
|
}
|