feat: 初始化shop-crm-agent项目,实现基础CLI与CRM操作能力

本提交初始化完整的shop-crm-agent项目:
1. 创建基础项目结构与配置文件(.gitignore、go.mod/go.sum)
2. 添加项目许可协议与文档(README.md、AGENTS.md、SKILL.md)
3. 实现核心CLI入口与命令框架
4. 封装Protobuf API客户端与完整的命令实现
5. 添加单元测试覆盖核心功能与边界情况
6. 提供构建脚本与版本管理能力
This commit is contained in:
2026-08-01 01:36:37 +08:00
commit 3466e19d13
15 changed files with 8554 additions and 0 deletions
+455
View File
@@ -0,0 +1,455 @@
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
}
+94
View File
@@ -0,0 +1,94 @@
package command
import (
"bytes"
"errors"
"testing"
dto "code.zhecent.com/open/shop-crm-agent/internal/api"
"google.golang.org/protobuf/encoding/protojson"
)
func TestRootCommandExposesOnlyCrmOperations(t *testing.T) {
cmd := New("test-version")
want := map[string]bool{
"customer-list": true, "project": true,
"quotation": true, "catalog-search": true, "edit": true,
}
for _, child := range cmd.Commands() {
if child.Name() == "credential" {
t.Fatal("external client exposes credential administration")
}
if child.Name() == "progress-template-list" {
t.Fatal("external client exposes retired progress template query")
}
delete(want, child.Name())
}
if len(want) != 0 {
t.Fatalf("missing root commands = %v", want)
}
if cmd.Version != "test-version" {
t.Fatalf("version = %q", cmd.Version)
}
}
func TestProjectCommandsExcludeProgressAndTemplateFlag(t *testing.T) {
root := New("test-version")
project, _, err := root.Find([]string{"project"})
if err != nil {
t.Fatal(err)
}
for _, child := range project.Commands() {
if child.Name() == "progress-complete" {
t.Fatal("project command exposes retired progress completion")
}
}
for _, name := range []string{"create", "update"} {
command, _, findErr := root.Find([]string{"project", name})
if findErr != nil {
t.Fatal(findErr)
}
if command.Flags().Lookup("progress-template-key") != nil {
t.Fatalf("project %s exposes retired progress template flag", name)
}
}
}
func TestRemoteCommandRequiresEnvironmentConfiguration(t *testing.T) {
t.Setenv("LIGHTCORE_API_BASE_URL", "")
t.Setenv("LIGHTCORE_SHOP_CRM_AGENT_TOKEN", "")
cmd := New("test")
cmd.SetArgs([]string{"customer-list"})
if err := cmd.Execute(); err == nil {
t.Fatal("missing environment configuration was accepted")
}
}
func TestWriteCrmAgentResultPrintsStructuredBusinessError(t *testing.T) {
commandErr := errors.New("CRM Agent API 错误 400: 报价已锁定")
response := &dto.ProjectResponse{Header: &dto.ResponseHeader{
Code: 400, Message: "报价已锁定", RequestId: "request-123", Replayed: true,
}}
var output bytes.Buffer
err := writeCrmAgentResult(&output, response, commandErr)
if !errors.Is(err, commandErr) {
t.Fatalf("command error = %v", err)
}
printed := &dto.ProjectResponse{}
if err := protojson.Unmarshal(output.Bytes(), printed); err != nil {
t.Fatalf("decode output: %v", err)
}
if printed.GetHeader() == nil || printed.GetHeader().Code != 400 || printed.GetHeader().RequestId != "request-123" || !printed.GetHeader().Replayed {
t.Fatalf("printed header = %#v", printed.GetHeader())
}
}
func TestWriteCrmAgentResultSkipsMissingTransportResponse(t *testing.T) {
commandErr := errors.New("connection refused")
var output bytes.Buffer
err := writeCrmAgentResult(&output, &dto.ProjectResponse{}, commandErr)
if !errors.Is(err, commandErr) || output.Len() != 0 {
t.Fatalf("error=%v output=%q", err, output.String())
}
}
+568
View File
@@ -0,0 +1,568 @@
package command
import (
"errors"
dto "code.zhecent.com/open/shop-crm-agent/internal/api"
"github.com/spf13/cobra"
)
func newCrmAgentEditCmd() *cobra.Command {
cmd := &cobra.Command{Use: "edit", Short: "通过服务端会话编辑报价", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentEditBeginCmd(), newCrmAgentEditPreviewCmd(), newCrmAgentEditCommitCmd(), newCrmAgentEditDiscardCmd(), newCrmAgentEditFloorCmd(), newCrmAgentEditSpaceCmd(), newCrmAgentEditCategoryCmd(), newCrmAgentEditLineCmd(), newCrmAgentEditFeeCmd(), newCrmAgentEditPricingCmd())
return cmd
}
func newCrmAgentEditBeginCmd() *cobra.Command {
var quotationID uint64
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "begin", Short: "开始报价编辑会话", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditBegin(cmd.Context(), &dto.QuotationEditBeginRequest{Command: crmAgentCommandHeader(write), QuotationId: quotationID})
return printCrmAgentResult(response, err)
}}
cmd.Flags().Uint64Var(&quotationID, "quotation-id", 0, "报价 ID")
_ = cmd.MarkFlagRequired("quotation-id")
bindCrmAgentWrite(cmd, &write)
return cmd
}
func newCrmAgentEditPreviewCmd() *cobra.Command {
var sessionID string
cmd := &cobra.Command{Use: "preview", Short: "预览编辑并生成 changeSetId", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditPreview(cmd.Context(), &dto.QuotationEditPreviewRequest{SessionId: sessionID})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
return cmd
}
func newCrmAgentEditCommitCmd() *cobra.Command {
var sessionID, changeSet string
var revision int32
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "commit", Short: "按 revision 和 changeSetId 提交编辑", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditCommit(cmd.Context(), &dto.QuotationEditCommitRequest{Command: crmAgentCommandHeader(write), SessionId: sessionID, Revision: revision, ChangeSetId: changeSet})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
cmd.Flags().Int32Var(&revision, "revision", 0, "preview 返回的 revision")
cmd.Flags().StringVar(&changeSet, "change-set-id", "", "preview 返回的 changeSetId")
_ = cmd.MarkFlagRequired("revision")
_ = cmd.MarkFlagRequired("change-set-id")
bindCrmAgentWrite(cmd, &write)
return cmd
}
func newCrmAgentEditDiscardCmd() *cobra.Command {
var sessionID string
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "discard", Short: "丢弃编辑会话", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditDiscard(cmd.Context(), &dto.QuotationEditDiscardRequest{Command: crmAgentCommandHeader(write), SessionId: sessionID})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
bindCrmAgentWrite(cmd, &write)
return cmd
}
func bindSessionID(cmd *cobra.Command, value *string) {
cmd.Flags().StringVar(value, "session-id", "", "报价编辑会话 ID")
_ = cmd.MarkFlagRequired("session-id")
}
func newCrmAgentEditMutationCmd(use, short string, bind func(*cobra.Command), build func() *dto.QuotationEditCommandRequest) *cobra.Command {
var sessionID string
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: use, Short: short, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
request := build()
if request == nil {
return errors.New("编辑命令构造失败")
}
request.Command, request.SessionId = crmAgentCommandHeader(write), sessionID
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditCommand(cmd.Context(), request)
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
bindCrmAgentWrite(cmd, &write)
if bind != nil {
bind(cmd)
}
return cmd
}
func newCrmAgentEditFloorCmd() *cobra.Command {
cmd := &cobra.Command{Use: "floor", Short: "增改排序和移除楼层", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentFloorAddCmd(), newCrmAgentFloorRenameCmd(), newCrmAgentFloorReorderCmd(), newCrmAgentFloorRemoveCmd())
return cmd
}
func newCrmAgentFloorAddCmd() *cobra.Command {
var id, name string
var index int32 = -1
return newCrmAgentEditMutationCmd("add", "添加楼层", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "id", "", "可选稳定楼层 ID")
cmd.Flags().StringVar(&name, "name", "", "楼层名称")
cmd.Flags().Int32Var(&index, "index", -1, "插入索引,-1追加")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddFloor{AddFloor: &dto.AddFloor{Id: id, Name: name, Index: index}}}
})
}
func newCrmAgentFloorRenameCmd() *cobra.Command {
var id, name string
return newCrmAgentEditMutationCmd("rename", "重命名楼层", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "floor-id", "", "楼层 ID")
cmd.Flags().StringVar(&name, "name", "", "新名称")
_ = cmd.MarkFlagRequired("floor-id")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RenameFloor{RenameFloor: &dto.RenameFloor{FloorId: id, Name: name}}}
})
}
func newCrmAgentFloorReorderCmd() *cobra.Command {
var id string
var index int32
return newCrmAgentEditMutationCmd("reorder", "调整楼层顺序", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "floor-id", "", "楼层 ID")
cmd.Flags().Int32Var(&index, "index", 0, "目标索引")
_ = cmd.MarkFlagRequired("floor-id")
_ = cmd.MarkFlagRequired("index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_ReorderFloor{ReorderFloor: &dto.ReorderFloor{FloorId: id, Index: index}}}
})
}
func newCrmAgentFloorRemoveCmd() *cobra.Command {
var id string
var cascade bool
return newCrmAgentEditMutationCmd("remove", "移除楼层", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "floor-id", "", "楼层 ID")
cmd.Flags().BoolVar(&cascade, "cascade", false, "同时移除下属空间、分类和产品行")
_ = cmd.MarkFlagRequired("floor-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RemoveFloor{RemoveFloor: &dto.RemoveFloor{FloorId: id, Cascade: cascade}}}
})
}
func newCrmAgentEditSpaceCmd() *cobra.Command {
cmd := &cobra.Command{Use: "space", Short: "增改移动排序和移除空间", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentSpaceAddCmd(), newCrmAgentSpaceRenameCmd(), newCrmAgentSpaceMoveCmd(), newCrmAgentSpaceReorderCmd(), newCrmAgentSpaceRemoveCmd())
return cmd
}
func newCrmAgentSpaceAddCmd() *cobra.Command {
var id, floorID, name string
var index int32 = -1
return newCrmAgentEditMutationCmd("add", "添加空间", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "id", "", "可选稳定空间 ID")
cmd.Flags().StringVar(&floorID, "floor-id", "", "楼层 ID")
cmd.Flags().StringVar(&name, "name", "", "空间名称")
cmd.Flags().Int32Var(&index, "index", -1, "插入索引")
_ = cmd.MarkFlagRequired("floor-id")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddSpace{AddSpace: &dto.AddSpace{Id: id, FloorId: floorID, Name: name, Index: index}}}
})
}
func newCrmAgentSpaceRenameCmd() *cobra.Command {
var id, name string
return newCrmAgentEditMutationCmd("rename", "重命名空间", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "space-id", "", "空间 ID")
cmd.Flags().StringVar(&name, "name", "", "新名称")
_ = cmd.MarkFlagRequired("space-id")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RenameSpace{RenameSpace: &dto.RenameSpace{SpaceId: id, Name: name}}}
})
}
func newCrmAgentSpaceMoveCmd() *cobra.Command {
var id, floorID string
var index int32 = -1
return newCrmAgentEditMutationCmd("move", "移动空间到其他楼层", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "space-id", "", "空间 ID")
cmd.Flags().StringVar(&floorID, "target-floor-id", "", "目标楼层 ID")
cmd.Flags().Int32Var(&index, "index", -1, "目标索引")
_ = cmd.MarkFlagRequired("space-id")
_ = cmd.MarkFlagRequired("target-floor-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_MoveSpace{MoveSpace: &dto.MoveSpace{SpaceId: id, TargetFloorId: floorID, Index: index}}}
})
}
func newCrmAgentSpaceReorderCmd() *cobra.Command {
var id string
var index int32
return newCrmAgentEditMutationCmd("reorder", "调整空间顺序", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "space-id", "", "空间 ID")
cmd.Flags().Int32Var(&index, "index", 0, "目标索引")
_ = cmd.MarkFlagRequired("space-id")
_ = cmd.MarkFlagRequired("index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_ReorderSpace{ReorderSpace: &dto.ReorderSpace{SpaceId: id, Index: index}}}
})
}
func newCrmAgentSpaceRemoveCmd() *cobra.Command {
var id string
var cascade bool
return newCrmAgentEditMutationCmd("remove", "移除空间", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "space-id", "", "空间 ID")
cmd.Flags().BoolVar(&cascade, "cascade", false, "同时移除分类和产品行")
_ = cmd.MarkFlagRequired("space-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RemoveSpace{RemoveSpace: &dto.RemoveSpace{SpaceId: id, Cascade: cascade}}}
})
}
func newCrmAgentEditCategoryCmd() *cobra.Command {
cmd := &cobra.Command{Use: "category", Short: "增改排序和移除分类", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentCategoryAddCmd(), newCrmAgentCategoryRenameCmd(), newCrmAgentCategoryReorderCmd(), newCrmAgentCategoryRemoveCmd())
return cmd
}
func newCrmAgentCategoryAddCmd() *cobra.Command {
var id, floorID, spaceID, name, source, sourceKey string
var index int32 = -1
return newCrmAgentEditMutationCmd("add", "添加分类", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "id", "", "可选稳定分类 ID")
cmd.Flags().StringVar(&floorID, "floor-id", "", "楼层 ID")
cmd.Flags().StringVar(&spaceID, "space-id", "", "空间 ID")
cmd.Flags().StringVar(&name, "name", "", "分类名称")
cmd.Flags().StringVar(&source, "source", "custom", "来源")
cmd.Flags().StringVar(&sourceKey, "source-key", "", "来源 key")
cmd.Flags().Int32Var(&index, "index", -1, "插入索引")
_ = cmd.MarkFlagRequired("floor-id")
_ = cmd.MarkFlagRequired("space-id")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddCategory{AddCategory: &dto.AddCategory{Id: id, FloorId: floorID, SpaceId: spaceID, Name: name, Source: source, SourceKey: sourceKey, Index: index}}}
})
}
func newCrmAgentCategoryRenameCmd() *cobra.Command {
var id, name string
return newCrmAgentEditMutationCmd("rename", "重命名分类", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "category-id", "", "分类 ID")
cmd.Flags().StringVar(&name, "name", "", "新名称")
_ = cmd.MarkFlagRequired("category-id")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RenameCategory{RenameCategory: &dto.RenameCategory{CategoryId: id, Name: name}}}
})
}
func newCrmAgentCategoryReorderCmd() *cobra.Command {
var id string
var index int32
return newCrmAgentEditMutationCmd("reorder", "调整分类顺序", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "category-id", "", "分类 ID")
cmd.Flags().Int32Var(&index, "index", 0, "目标索引")
_ = cmd.MarkFlagRequired("category-id")
_ = cmd.MarkFlagRequired("index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_ReorderCategory{ReorderCategory: &dto.ReorderCategory{CategoryId: id, Index: index}}}
})
}
func newCrmAgentCategoryRemoveCmd() *cobra.Command {
var id string
var cascade bool
return newCrmAgentEditMutationCmd("remove", "移除分类", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&id, "category-id", "", "分类 ID")
cmd.Flags().BoolVar(&cascade, "cascade", false, "同时移除产品行")
_ = cmd.MarkFlagRequired("category-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RemoveCategory{RemoveCategory: &dto.RemoveCategory{CategoryId: id, Cascade: cascade}}}
})
}
func newCrmAgentEditLineCmd() *cobra.Command {
cmd := &cobra.Command{Use: "line", Short: "增改移动排序启停和移除产品行", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentLineAddCatalogCmd(), newCrmAgentLineAddCustomCmd(), newCrmAgentLineUpdateCmd(), newCrmAgentLineMoveCmd(), newCrmAgentLineReorderCmd(), newCrmAgentLineActiveCmd(true), newCrmAgentLineActiveCmd(false), newCrmAgentLineRemoveCmd())
return cmd
}
type crmAgentLineTarget struct {
floorID, spaceID, categoryID string
index int32
}
func bindLineTarget(cmd *cobra.Command, target *crmAgentLineTarget) {
cmd.Flags().StringVar(&target.floorID, "floor-id", "", "楼层 ID")
cmd.Flags().StringVar(&target.spaceID, "space-id", "", "空间 ID")
cmd.Flags().StringVar(&target.categoryID, "category-id", "", "分类 ID")
cmd.Flags().Int32Var(&target.index, "index", -1, "分类内索引,-1追加")
_ = cmd.MarkFlagRequired("floor-id")
_ = cmd.MarkFlagRequired("space-id")
_ = cmd.MarkFlagRequired("category-id")
}
func newCrmAgentLineAddCatalogCmd() *cobra.Command {
var target crmAgentLineTarget
var skuID uint64
var quantity float64 = 1
var discountRate int32 = 100
var discountAmount int64
var remark string
return newCrmAgentEditMutationCmd("add-catalog", "按 SKU 添加产品库行", func(cmd *cobra.Command) {
bindLineTarget(cmd, &target)
cmd.Flags().Uint64Var(&skuID, "sku-id", 0, "产品库 SKU ID")
cmd.Flags().Float64Var(&quantity, "quantity", 1, "数量")
cmd.Flags().Int32Var(&discountRate, "discount-rate", 100, "折扣率0-100")
cmd.Flags().Int64Var(&discountAmount, "discount-amount", 0, "优惠金额(分)")
cmd.Flags().StringVar(&remark, "remark", "", "备注")
_ = cmd.MarkFlagRequired("sku-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddCatalogLine{AddCatalogLine: &dto.AddCatalogLine{FloorId: target.floorID, SpaceId: target.spaceID, CategoryId: target.categoryID, SkuId: skuID, Quantity: quantity, DiscountRate: discountRate, DiscountAmount: discountAmount, Remark: remark, Index: target.index}}}
})
}
func newCrmAgentLineAddCustomCmd() *cobra.Command {
var target crmAgentLineTarget
var name, spec, remark string
var quantity float64 = 1
var unitPrice, costPrice, discountAmount int64
var discountRate int32 = 100
return newCrmAgentEditMutationCmd("add-custom", "添加自定义产品行", func(cmd *cobra.Command) {
bindLineTarget(cmd, &target)
cmd.Flags().StringVar(&name, "name", "", "产品名称")
cmd.Flags().StringVar(&spec, "spec", "", "规格")
cmd.Flags().Float64Var(&quantity, "quantity", 1, "数量")
cmd.Flags().Int64Var(&unitPrice, "unit-price", 0, "售价(分)")
cmd.Flags().Int64Var(&costPrice, "cost-price", 0, "成本(分)")
cmd.Flags().Int32Var(&discountRate, "discount-rate", 100, "折扣率0-100")
cmd.Flags().Int64Var(&discountAmount, "discount-amount", 0, "优惠金额(分)")
cmd.Flags().StringVar(&remark, "remark", "", "备注")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddCustomLine{AddCustomLine: &dto.AddCustomLine{FloorId: target.floorID, SpaceId: target.spaceID, CategoryId: target.categoryID, GoodsName: name, SpecCombination: spec, Quantity: quantity, UnitPrice: unitPrice, CostPrice: costPrice, DiscountRate: discountRate, DiscountAmount: discountAmount, Remark: remark, Index: target.index}}}
})
}
func newCrmAgentLineUpdateCmd() *cobra.Command {
var sessionID, lineID, name, spec, remark string
var quantity float64
var unitPrice, costPrice, discountAmount int64
var discountRate int32
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "update", Short: "按已提供 flags 更新产品行", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
update := &dto.UpdateLine{LineId: lineID}
if cmd.Flags().Changed("name") {
update.GoodsName = &name
}
if cmd.Flags().Changed("spec") {
update.SpecCombination = &spec
}
if cmd.Flags().Changed("quantity") {
update.Quantity = &quantity
}
if cmd.Flags().Changed("unit-price") {
update.UnitPrice = &unitPrice
}
if cmd.Flags().Changed("cost-price") {
update.CostPrice = &costPrice
}
if cmd.Flags().Changed("discount-rate") {
update.DiscountRate = &discountRate
}
if cmd.Flags().Changed("discount-amount") {
update.DiscountAmount = &discountAmount
}
if cmd.Flags().Changed("remark") {
update.Remark = &remark
}
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditCommand(cmd.Context(), &dto.QuotationEditCommandRequest{Command: crmAgentCommandHeader(write), SessionId: sessionID, Edit: &dto.QuotationEditCommandRequest_UpdateLine{UpdateLine: update}})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
bindCrmAgentWrite(cmd, &write)
cmd.Flags().StringVar(&lineID, "line-id", "", "产品行 ID")
cmd.Flags().StringVar(&name, "name", "", "产品名称(仅自定义行)")
cmd.Flags().StringVar(&spec, "spec", "", "规格(仅自定义行)")
cmd.Flags().Float64Var(&quantity, "quantity", 0, "数量")
cmd.Flags().Int64Var(&unitPrice, "unit-price", 0, "售价(分)")
cmd.Flags().Int64Var(&costPrice, "cost-price", 0, "成本(分,仅自定义行)")
cmd.Flags().Int32Var(&discountRate, "discount-rate", 0, "折扣率0-100")
cmd.Flags().Int64Var(&discountAmount, "discount-amount", 0, "优惠金额(分)")
cmd.Flags().StringVar(&remark, "remark", "", "备注")
_ = cmd.MarkFlagRequired("line-id")
return cmd
}
func newCrmAgentLineMoveCmd() *cobra.Command {
var target crmAgentLineTarget
var lineID string
return newCrmAgentEditMutationCmd("move", "移动产品行", func(cmd *cobra.Command) {
bindLineTarget(cmd, &target)
cmd.Flags().StringVar(&lineID, "line-id", "", "产品行 ID")
_ = cmd.MarkFlagRequired("line-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_MoveLine{MoveLine: &dto.MoveLine{LineId: lineID, FloorId: target.floorID, SpaceId: target.spaceID, CategoryId: target.categoryID, Index: target.index}}}
})
}
func newCrmAgentLineReorderCmd() *cobra.Command {
var lineID string
var index int32
return newCrmAgentEditMutationCmd("reorder", "调整产品行在分类内的顺序", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&lineID, "line-id", "", "产品行 ID")
cmd.Flags().Int32Var(&index, "index", 0, "目标索引")
_ = cmd.MarkFlagRequired("line-id")
_ = cmd.MarkFlagRequired("index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_ReorderLine{ReorderLine: &dto.ReorderLine{LineId: lineID, Index: index}}}
})
}
func newCrmAgentLineActiveCmd(active bool) *cobra.Command {
use, short := "disable", "停用产品行"
if active {
use, short = "enable", "启用产品行"
}
var lineID string
return newCrmAgentEditMutationCmd(use, short, func(cmd *cobra.Command) {
cmd.Flags().StringVar(&lineID, "line-id", "", "产品行 ID")
_ = cmd.MarkFlagRequired("line-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_SetLineActive{SetLineActive: &dto.SetLineActive{LineId: lineID, Active: active}}}
})
}
func newCrmAgentLineRemoveCmd() *cobra.Command {
var lineID string
return newCrmAgentEditMutationCmd("remove", "移除产品行", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&lineID, "line-id", "", "产品行 ID")
_ = cmd.MarkFlagRequired("line-id")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RemoveLine{RemoveLine: &dto.RemoveLine{LineId: lineID}}}
})
}
func newCrmAgentEditFeeCmd() *cobra.Command {
cmd := &cobra.Command{Use: "fee", Short: "增改排序和移除费用", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return cmd.Help() }}
cmd.AddCommand(newCrmAgentFeeAddCmd(), newCrmAgentFeeUpdateCmd(), newCrmAgentFeeReorderCmd(), newCrmAgentFeeRemoveCmd())
return cmd
}
func newCrmAgentFeeAddCmd() *cobra.Command {
var name, feeType, remark string
var fixedAmount int64
var percentage, index int32
index = -1
return newCrmAgentEditMutationCmd("add", "添加费用", func(cmd *cobra.Command) {
cmd.Flags().StringVar(&name, "name", "", "费用名称")
cmd.Flags().StringVar(&feeType, "type", "fixed", "fixed/discounted_percent/original_percent")
cmd.Flags().Int64Var(&fixedAmount, "fixed-amount", 0, "固定金额(分)")
cmd.Flags().Int32Var(&percentage, "percentage", 0, "百分比0-100")
cmd.Flags().Int32Var(&index, "index", -1, "插入索引")
cmd.Flags().StringVar(&remark, "remark", "", "备注")
_ = cmd.MarkFlagRequired("name")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_AddFee{AddFee: &dto.AddFee{Name: name, Type: feeType, FixedAmount: fixedAmount, Percentage: percentage, Index: index, Remark: remark}}}
})
}
func newCrmAgentFeeUpdateCmd() *cobra.Command {
var sessionID, name, feeType, remark string
var index, percentage int32
var fixedAmount int64
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "update", Short: "按已提供 flags 更新费用", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
update := &dto.UpdateFee{Index: index}
if cmd.Flags().Changed("name") {
update.Name = &name
}
if cmd.Flags().Changed("type") {
update.Type = &feeType
}
if cmd.Flags().Changed("fixed-amount") {
update.FixedAmount = &fixedAmount
}
if cmd.Flags().Changed("percentage") {
update.Percentage = &percentage
}
if cmd.Flags().Changed("remark") {
update.Remark = &remark
}
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditCommand(cmd.Context(), &dto.QuotationEditCommandRequest{Command: crmAgentCommandHeader(write), SessionId: sessionID, Edit: &dto.QuotationEditCommandRequest_UpdateFee{UpdateFee: update}})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
bindCrmAgentWrite(cmd, &write)
cmd.Flags().Int32Var(&index, "index", -1, "费用索引")
cmd.Flags().StringVar(&name, "name", "", "名称")
cmd.Flags().StringVar(&feeType, "type", "", "费用类型")
cmd.Flags().Int64Var(&fixedAmount, "fixed-amount", 0, "固定金额(分)")
cmd.Flags().Int32Var(&percentage, "percentage", 0, "百分比0-100")
cmd.Flags().StringVar(&remark, "remark", "", "备注")
_ = cmd.MarkFlagRequired("index")
return cmd
}
func newCrmAgentFeeReorderCmd() *cobra.Command {
var index, target int32
return newCrmAgentEditMutationCmd("reorder", "调整费用顺序", func(cmd *cobra.Command) {
cmd.Flags().Int32Var(&index, "index", -1, "当前索引")
cmd.Flags().Int32Var(&target, "target-index", 0, "目标索引")
_ = cmd.MarkFlagRequired("index")
_ = cmd.MarkFlagRequired("target-index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_ReorderFee{ReorderFee: &dto.ReorderFee{Index: index, TargetIndex: target}}}
})
}
func newCrmAgentFeeRemoveCmd() *cobra.Command {
var index int32
return newCrmAgentEditMutationCmd("remove", "移除费用", func(cmd *cobra.Command) {
cmd.Flags().Int32Var(&index, "index", -1, "费用索引")
_ = cmd.MarkFlagRequired("index")
}, func() *dto.QuotationEditCommandRequest {
return &dto.QuotationEditCommandRequest{Edit: &dto.QuotationEditCommandRequest_RemoveFee{RemoveFee: &dto.RemoveFee{Index: index}}}
})
}
func newCrmAgentEditPricingCmd() *cobra.Command {
var sessionID, remark string
var discountRate int32
var discountAmount, actualAmount int64
var adjusted bool
var write crmAgentWriteOptions
cmd := &cobra.Command{Use: "pricing", Short: "修改整单折扣、优惠、实际金额或调价标记", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error {
pricing := &dto.SetPricing{}
if cmd.Flags().Changed("discount-rate") {
pricing.DiscountRate = &discountRate
}
if cmd.Flags().Changed("discount-amount") {
pricing.DiscountAmount = &discountAmount
}
if cmd.Flags().Changed("actual-amount") {
pricing.ActualAmount = &actualAmount
}
if cmd.Flags().Changed("adjusted") {
pricing.HasPriceAdjustment = &adjusted
}
if cmd.Flags().Changed("remark") {
pricing.Remark = &remark
}
client, err := newCrmAgentHTTPClient()
if err != nil {
return err
}
response, err := client.EditCommand(cmd.Context(), &dto.QuotationEditCommandRequest{Command: crmAgentCommandHeader(write), SessionId: sessionID, Edit: &dto.QuotationEditCommandRequest_SetPricing{SetPricing: pricing}})
return printCrmAgentResult(response, err)
}}
bindSessionID(cmd, &sessionID)
bindCrmAgentWrite(cmd, &write)
cmd.Flags().Int32Var(&discountRate, "discount-rate", 0, "整单折扣率0-100")
cmd.Flags().Int64Var(&discountAmount, "discount-amount", 0, "整单优惠金额(分)")
cmd.Flags().Int64Var(&actualAmount, "actual-amount", 0, "实际成交金额(分)")
cmd.Flags().BoolVar(&adjusted, "adjusted", false, "是否标记人工调价")
cmd.Flags().StringVar(&remark, "remark", "", "报价备注")
return cmd
}