package command import ( "fmt" "io" "os" "strings" dto "code.zhecent.com/open/shop-crm-agent/internal/api" crmClient "code.zhecent.com/open/shop-crm-agent/internal/client" "github.com/spf13/cobra" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" ) type crmAgentWriteOptions struct{ requestID string } func New(version string) *cobra.Command { cmd := &cobra.Command{ Use: "shop-crm-agent", Short: "通过 Protobuf API 操作 Shop CRM", Version: version, Args: cobra.NoArgs, SilenceErrors: true, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }, } cmd.AddCommand(newCrmAgentCustomerListCmd(), newCrmAgentProjectCmd(), newCrmAgentQuotationCmd(), newCrmAgentCatalogCmd(), newCrmAgentEditCmd()) return cmd } func newCrmAgentHTTPClient() (*crmClient.Client, error) { return crmClient.New(os.Getenv("LIGHTCORE_API_BASE_URL"), os.Getenv("LIGHTCORE_SHOP_CRM_AGENT_TOKEN"), nil) } func writeCrmAgentProto(writer io.Writer, message proto.Message) error { data, err := (protojson.MarshalOptions{Indent: " ", EmitUnpopulated: true}).Marshal(message) if err != nil { return err } _, err = fmt.Fprintln(writer, string(data)) return err } type crmAgentHeaderResponse interface { proto.Message GetHeader() *dto.ResponseHeader } func writeCrmAgentResult(writer io.Writer, response crmAgentHeaderResponse, commandErr error) error { if response != nil && response.GetHeader() != nil { if err := writeCrmAgentProto(writer, response); err != nil { return err } } return commandErr } func printCrmAgentResult(response crmAgentHeaderResponse, commandErr error) error { return writeCrmAgentResult(os.Stdout, response, commandErr) } func bindCrmAgentWrite(cmd *cobra.Command, options *crmAgentWriteOptions) { cmd.Flags().StringVar(&options.requestID, "request-id", "", "8-80位幂等请求 ID") _ = cmd.MarkFlagRequired("request-id") } func crmAgentCommandHeader(options crmAgentWriteOptions) *dto.CommandHeader { return &dto.CommandHeader{RequestId: strings.TrimSpace(options.requestID)} } func newCrmAgentCustomerListCmd() *cobra.Command { var keyword string var page, pageSize int32 cmd := &cobra.Command{Use: "customer-list", Short: "查询创建项目可引用的 CRM 客户", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.CustomerList(cmd.Context(), &dto.CustomerListRequest{Keyword: keyword, Page: page, PageSize: pageSize}) return printCrmAgentResult(response, err) }} cmd.Flags().StringVar(&keyword, "keyword", "", "客户姓名或手机号") cmd.Flags().Int32Var(&page, "page", 1, "页码") cmd.Flags().Int32Var(&pageSize, "page-size", 20, "每页数量") return cmd } func newCrmAgentProjectCmd() *cobra.Command { cmd := &cobra.Command{Use: "project", Short: "查询和维护 CRM 项目", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }} cmd.AddCommand(newCrmAgentProjectListCmd(), newCrmAgentProjectGetCmd(), newCrmAgentProjectCreateCmd(), newCrmAgentProjectUpdateCmd(), newCrmAgentProjectStatusCmd(), newCrmAgentProjectDeletePreviewCmd(), newCrmAgentProjectDeleteCmd()) return cmd } func newCrmAgentProjectListCmd() *cobra.Command { var keyword, status, houseType string var page, pageSize int32 cmd := &cobra.Command{Use: "list", Short: "分页查询项目", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectList(cmd.Context(), &dto.ProjectListRequest{Keyword: keyword, Status: status, HouseType: houseType, Page: page, PageSize: pageSize}) return printCrmAgentResult(response, err) }} cmd.Flags().StringVar(&keyword, "keyword", "", "项目关键词") cmd.Flags().StringVar(&status, "status", "", "项目状态") cmd.Flags().StringVar(&houseType, "house-type", "", "房屋类型") cmd.Flags().Int32Var(&page, "page", 1, "页码") cmd.Flags().Int32Var(&pageSize, "page-size", 20, "每页数量") return cmd } func newCrmAgentProjectGetCmd() *cobra.Command { var projectID uint64 cmd := &cobra.Command{Use: "get", Short: "读取项目详情", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectGet(cmd.Context(), &dto.ProjectGetRequest{ProjectId: projectID}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") _ = cmd.MarkFlagRequired("project-id") return cmd } type crmAgentProjectFields struct { name string customerID uint64 houseType, province, provinceCode, city, cityCode, district, districtCode, address, remark string } func bindCrmAgentProjectFields(cmd *cobra.Command, fields *crmAgentProjectFields, includeCustomer, requireCore bool) { cmd.Flags().StringVar(&fields.name, "name", "", "项目名称") cmd.Flags().StringVar(&fields.houseType, "house-type", "", "房屋类型: flat/duplex/villa/commercial/other") if includeCustomer { cmd.Flags().Uint64Var(&fields.customerID, "customer-id", 0, "客户 ID") _ = cmd.MarkFlagRequired("customer-id") } cmd.Flags().StringVar(&fields.province, "province", "", "省份") cmd.Flags().StringVar(&fields.provinceCode, "province-code", "", "省份编码") cmd.Flags().StringVar(&fields.city, "city", "", "城市") cmd.Flags().StringVar(&fields.cityCode, "city-code", "", "城市编码") cmd.Flags().StringVar(&fields.district, "district", "", "区县") cmd.Flags().StringVar(&fields.districtCode, "district-code", "", "区县编码") cmd.Flags().StringVar(&fields.address, "address", "", "详细地址") cmd.Flags().StringVar(&fields.remark, "remark", "", "备注") if requireCore { _ = cmd.MarkFlagRequired("name") _ = cmd.MarkFlagRequired("house-type") } } func newCrmAgentProjectCreateCmd() *cobra.Command { var fields crmAgentProjectFields var write crmAgentWriteOptions cmd := &cobra.Command{Use: "create", Short: "创建项目", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectCreate(cmd.Context(), &dto.ProjectCreateRequest{Command: crmAgentCommandHeader(write), Name: fields.name, CustomerId: fields.customerID, HouseType: fields.houseType, Province: fields.province, ProvinceCode: fields.provinceCode, City: fields.city, CityCode: fields.cityCode, District: fields.district, DistrictCode: fields.districtCode, Address: fields.address, Remark: fields.remark}) return printCrmAgentResult(response, err) }} bindCrmAgentProjectFields(cmd, &fields, true, true) bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentProjectUpdateCmd() *cobra.Command { var projectID uint64 var fields crmAgentProjectFields var write crmAgentWriteOptions cmd := &cobra.Command{Use: "update", Short: "更新项目资料", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectUpdate(cmd.Context(), &dto.ProjectUpdateRequest{ Command: crmAgentCommandHeader(write), ProjectId: projectID, Name: crmAgentChangedString(cmd, "name", fields.name), HouseType: crmAgentChangedString(cmd, "house-type", fields.houseType), Province: crmAgentChangedString(cmd, "province", fields.province), ProvinceCode: crmAgentChangedString(cmd, "province-code", fields.provinceCode), City: crmAgentChangedString(cmd, "city", fields.city), CityCode: crmAgentChangedString(cmd, "city-code", fields.cityCode), District: crmAgentChangedString(cmd, "district", fields.district), DistrictCode: crmAgentChangedString(cmd, "district-code", fields.districtCode), Address: crmAgentChangedString(cmd, "address", fields.address), Remark: crmAgentChangedString(cmd, "remark", fields.remark), }) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") _ = cmd.MarkFlagRequired("project-id") bindCrmAgentProjectFields(cmd, &fields, false, false) bindCrmAgentWrite(cmd, &write) return cmd } func crmAgentChangedString(cmd *cobra.Command, flagName, value string) *string { if !cmd.Flags().Changed(flagName) { return nil } return &value } func newCrmAgentProjectStatusCmd() *cobra.Command { var projectID uint64 var status string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "status", Short: "修改项目状态", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectStatus(cmd.Context(), &dto.ProjectStatusRequest{Command: crmAgentCommandHeader(write), ProjectId: projectID, Status: status}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") cmd.Flags().StringVar(&status, "status", "", "状态: active/completed/shelved") _ = cmd.MarkFlagRequired("project-id") _ = cmd.MarkFlagRequired("status") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentProjectDeletePreviewCmd() *cobra.Command { var projectID uint64 cmd := &cobra.Command{Use: "delete-preview", Short: "预览空项目删除", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectDeletePreview(cmd.Context(), &dto.ProjectDeletePreviewRequest{ProjectId: projectID}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") _ = cmd.MarkFlagRequired("project-id") return cmd } func newCrmAgentProjectDeleteCmd() *cobra.Command { var projectID uint64 var changeSet string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "delete", Short: "删除已预览的空项目", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.ProjectDelete(cmd.Context(), &dto.ProjectDeleteRequest{Command: crmAgentCommandHeader(write), ProjectId: projectID, ChangeSetId: changeSet}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") cmd.Flags().StringVar(&changeSet, "change-set-id", "", "delete-preview 返回的 changeSetId") _ = cmd.MarkFlagRequired("project-id") _ = cmd.MarkFlagRequired("change-set-id") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationCmd() *cobra.Command { cmd := &cobra.Command{Use: "quotation", Short: "查询和维护独立报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }} cmd.AddCommand(newCrmAgentQuotationListCmd(), newCrmAgentQuotationGetCmd(), newCrmAgentQuotationCreateCmd(), newCrmAgentQuotationCopyCmd(), newCrmAgentQuotationTitleCmd(), newCrmAgentQuotationStatusCmd(), newCrmAgentQuotationLockCmd(true), newCrmAgentQuotationLockCmd(false), newCrmAgentQuotationDeletePreviewCmd(), newCrmAgentQuotationDeleteCmd()) return cmd } func newCrmAgentQuotationListCmd() *cobra.Command { var projectID uint64 var keyword, status string var page, pageSize int32 cmd := &cobra.Command{Use: "list", Short: "分页查询报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationList(cmd.Context(), &dto.QuotationListRequest{ProjectId: projectID, Keyword: keyword, Status: status, Page: page, PageSize: pageSize}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") cmd.Flags().StringVar(&keyword, "keyword", "", "关键词") cmd.Flags().StringVar(&status, "status", "", "报价状态") cmd.Flags().Int32Var(&page, "page", 1, "页码") cmd.Flags().Int32Var(&pageSize, "page-size", 20, "每页数量") return cmd } func newCrmAgentQuotationGetCmd() *cobra.Command { var id uint64 cmd := &cobra.Command{Use: "get", Short: "读取报价详情", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationGet(cmd.Context(), &dto.QuotationGetRequest{QuotationId: id}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") _ = cmd.MarkFlagRequired("quotation-id") return cmd } func newCrmAgentQuotationCreateCmd() *cobra.Command { var projectID uint64 var title string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "create", Short: "创建空报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationCreate(cmd.Context(), &dto.QuotationCreateRequest{Command: crmAgentCommandHeader(write), ProjectId: projectID, Title: title}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&projectID, "project-id", 0, "项目 ID") cmd.Flags().StringVar(&title, "title", "报价", "报价标题") _ = cmd.MarkFlagRequired("project-id") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationCopyCmd() *cobra.Command { var id uint64 var title string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "copy", Short: "复制独立报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationCopy(cmd.Context(), &dto.QuotationCopyRequest{Command: crmAgentCommandHeader(write), QuotationId: id, Title: title}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "源报价 ID") cmd.Flags().StringVar(&title, "title", "", "副本标题") _ = cmd.MarkFlagRequired("quotation-id") _ = cmd.MarkFlagRequired("title") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationTitleCmd() *cobra.Command { var id uint64 var title string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "title", Short: "修改报价标题", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationTitle(cmd.Context(), &dto.QuotationTitleRequest{Command: crmAgentCommandHeader(write), QuotationId: id, Title: title}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") cmd.Flags().StringVar(&title, "title", "", "标题") _ = cmd.MarkFlagRequired("quotation-id") _ = cmd.MarkFlagRequired("title") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationStatusCmd() *cobra.Command { var id uint64 var status string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "status", Short: "修改报价状态", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationStatus(cmd.Context(), &dto.QuotationStatusRequest{Command: crmAgentCommandHeader(write), QuotationId: id, Status: status}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") cmd.Flags().StringVar(&status, "status", "", "报价状态") _ = cmd.MarkFlagRequired("quotation-id") _ = cmd.MarkFlagRequired("status") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationLockCmd(locked bool) *cobra.Command { use, short := "unlock", "解锁报价" if locked { use, short = "lock", "锁定报价" } var id uint64 var write crmAgentWriteOptions cmd := &cobra.Command{Use: use, Short: short, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } request := &dto.QuotationLockRequest{Command: crmAgentCommandHeader(write), QuotationId: id} var response *dto.QuotationResponse if locked { response, err = client.QuotationLock(cmd.Context(), request) } else { response, err = client.QuotationUnlock(cmd.Context(), request) } return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") _ = cmd.MarkFlagRequired("quotation-id") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentQuotationDeletePreviewCmd() *cobra.Command { var id uint64 cmd := &cobra.Command{Use: "delete-preview", Short: "预览报价删除", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationDeletePreview(cmd.Context(), &dto.QuotationDeletePreviewRequest{QuotationId: id}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") _ = cmd.MarkFlagRequired("quotation-id") return cmd } func newCrmAgentQuotationDeleteCmd() *cobra.Command { var id uint64 var revision int32 var changeSet string var write crmAgentWriteOptions cmd := &cobra.Command{Use: "delete", Short: "按 revision 删除报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.QuotationDelete(cmd.Context(), &dto.QuotationDeleteRequest{Command: crmAgentCommandHeader(write), QuotationId: id, Revision: revision, ChangeSetId: changeSet}) return printCrmAgentResult(response, err) }} cmd.Flags().Uint64Var(&id, "quotation-id", 0, "报价 ID") cmd.Flags().Int32Var(&revision, "revision", 0, "delete-preview 返回的 revision") cmd.Flags().StringVar(&changeSet, "change-set-id", "", "delete-preview 返回的 changeSetId") _ = cmd.MarkFlagRequired("quotation-id") _ = cmd.MarkFlagRequired("revision") _ = cmd.MarkFlagRequired("change-set-id") bindCrmAgentWrite(cmd, &write) return cmd } func newCrmAgentCatalogCmd() *cobra.Command { var keyword string var categoryID, rootCategoryID, brandID uint64 var page, pageSize int32 cmd := &cobra.Command{Use: "catalog-search", Short: "查询可加入报价的产品库 SKU", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { client, err := newCrmAgentHTTPClient() if err != nil { return err } response, err := client.CatalogSearch(cmd.Context(), &dto.CatalogSearchRequest{Keyword: keyword, CategoryId: categoryID, RootCategoryId: rootCategoryID, BrandId: brandID, Page: page, PageSize: pageSize}) return printCrmAgentResult(response, err) }} cmd.Flags().StringVar(&keyword, "keyword", "", "关键词") cmd.Flags().Uint64Var(&categoryID, "category-id", 0, "分类 ID") cmd.Flags().Uint64Var(&rootCategoryID, "root-category-id", 0, "根分类 ID") cmd.Flags().Uint64Var(&brandID, "brand-id", 0, "品牌 ID") cmd.Flags().Int32Var(&page, "page", 1, "页码") cmd.Flags().Int32Var(&pageSize, "page-size", 20, "每页数量") return cmd }