Преглед на файлове

feature:添加客户导入功能

sunxinyuan преди 1 година
родител
ревизия
384cd59b13
променени са 3 файла, в които са добавени 411 реда и са изтрити 8 реда
  1. 36 0
      opms_parent/app/handler/cust/customer.go
  2. 28 0
      opms_parent/app/model/cust/cust_customer.go
  3. 347 8
      opms_parent/app/service/cust/cust_customer.go

+ 36 - 0
opms_parent/app/handler/cust/customer.go

@@ -2,6 +2,8 @@ package cust
 
 import (
 	"context"
+	"dashoo.cn/micro/app/model/contract"
+	"encoding/base64"
 
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
@@ -270,3 +272,37 @@ func (c *CustomerHeader) SysAdminTransCustomer(ctx context.Context, req *model.A
 	}
 	return nil
 }
+
+// Swagger:Customer 导入客户 模板
+func (c *CustomerHeader) ExcelTemplate(ctx context.Context, req *struct{}, rsp *comm_def.CommonMsg) error {
+	//g.Log().Infof("TeQuestionDocDetail.ExcelTemplate request %#v ", *req)
+	f, err := server.ExcelTemplate()
+	if err != nil {
+		return err
+	}
+	buf, err := f.WriteToBuffer()
+	if err != nil {
+		return err
+	}
+	rsp.Data = base64.StdEncoding.EncodeToString(buf.Bytes())
+	return nil
+}
+
+// Swagger:Customer 客户 导入客户并创建联系人
+func (c *CustomerHeader) Import(ctx context.Context, req *contract.ExcelImportReq, rsp *comm_def.CommonMsg) error {
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return err
+	}
+	s, err := server.NewCustomerService(ctx)
+	if err != nil {
+
+		return err
+
+	}
+	err, failedCount, failedData := s.Import(ctx, req)
+	if err != nil {
+		return err
+	}
+	rsp.Data = g.Map{"failedCount": failedCount, "failedData": failedData}
+	return nil
+}

+ 28 - 0
opms_parent/app/model/cust/cust_customer.go

@@ -168,3 +168,31 @@ type IsExistsCustName struct {
 	Id       int    `json:"id,omitempty"` //客户id
 	CustName string `json:"custName"`     // 客户名称
 }
+type ImportFile struct {
+	Url string `json:"url"`
+}
+
+// CustomerAddSeq 导入客户信息表
+type CustomerAddImport struct {
+	CustName       string `import:"客户名称"  p:"custName"        json:"custName"      v:"required#客户名称不能为空"`                // 客户名称
+	AbbrName       string `import:"助计名"  p:"abbrName"        json:"abbrName"   `                                         // 助计名
+	CustIndustry   string `import:"客户类型" p:"custIndustry"    json:"custIndustry"  v:"required#客户行业不能为空"`                 // 客户行业
+	CustSource     string `import:"客户来源" p:"custSource"      json:"custSource"        v:"required#客户来源不能为空"`             // 客户来源
+	CustProvince   string `import:"所在省" json:"custProvince"`                                                             // 所在省
+	CustCity       string `import:"所在市" json:"custCity"`                                                                 // 所在市
+	CustRegion     string `import:"所在区县" json:"custRegion"`                                                              // 所在区县
+	CustAddress    string `import:"详细地址" p:"custAddress"     json:"custAddress"   `                                      // 详细地址
+	Keyword        string `import:"招标关键字" json:"keyword"`                                                                // 招标关键字
+	Remark         string `import:"备注" p:"remark"          json:"remark"`                                                // 备注
+	CuctName       string `import:"姓名" p:"cuctName"     json:"cuctName"  v:"required#联系人名字不能为空"`                         // 姓名
+	CuctGender     string `import:"性别" p:"cuctGender"    json:"cuctGender"  v:"required|in:10,20#性别不能为空|填写错误"`           // 性别(10男20女)
+	Telephone      string `import:"电话" p:"telephone"    json:"telephone"  v:"phone#手机号格式错误"`                             // 电话
+	Wechat         string `import:"微信" p:"wechat"    json:"wechat"`                                                      // 微信
+	Email          string `import:"邮箱" p:"email"    json:"email" v:"email#邮箱格式错误"`                                       // 邮箱
+	Dept           string `import:"部门" p:"dept"            json:"dept"`                                                  // 部门
+	Postion        string `import:"职位" p:"postion"    json:"postion"   v:"required#岗位不能为空" `                             // 职位
+	OfficeLocation string `import:"办公地点" p:"officeLocation" json:"officeLocation"`                                       // 办公地点
+	IsDecision     string `import:"是否关键决策人" p:"isDecision"     json:"isDecision" v:"required|in:10,20#是否关键决策人不能为空|填写错误"` // 关键决策人(10是20否)
+	ContactRemark  string `import:"联系人备注" p:"contactRemark"          json:"contactRemark"`                               // 备注
+	FailedReason   string `json:"failedReason"`
+}

+ 347 - 8
opms_parent/app/service/cust/cust_customer.go

@@ -3,24 +3,31 @@ package cust
 import (
 	"bytes"
 	"context"
-	"encoding/json"
-	"fmt"
-	"github.com/gogf/gf/container/garray"
-	"math"
-	"strconv"
-	"strings"
-	"time"
-
+	"dashoo.cn/micro/app/model/base"
+	"dashoo.cn/micro/app/model/contract"
+	server "dashoo.cn/micro/app/service/base"
+	"dashoo.cn/micro/app/service/partner"
 	"dashoo.cn/opms_libary/myerrors"
 	"dashoo.cn/opms_libary/plugin/dingtalk/message"
 	"dashoo.cn/opms_libary/request"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
 	"github.com/360EntSecGroup-Skylar/excelize"
+	"github.com/gogf/gf/container/garray"
 	"github.com/gogf/gf/database/gdb"
 	"github.com/gogf/gf/encoding/gjson"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
 	"github.com/gogf/gf/util/gconv"
+	"github.com/gogf/gf/util/gvalid"
 	"github.com/mozillazg/go-pinyin"
+	excelizev2 "github.com/xuri/excelize/v2"
+	"math"
+	"strconv"
+	"strings"
+	"time"
 
 	"dashoo.cn/micro/app/dao/cust"
 	platdao "dashoo.cn/micro/app/dao/plat"
@@ -1277,3 +1284,335 @@ func (s *CustomerService) Export(ctx context.Context, req *model.CustCustomerExp
 
 	return &con, err
 }
+func reverseMap(originalMap map[string]string) map[string]string {
+	reversedMap := make(map[string]string)
+	for key, value := range originalMap {
+		reversedMap[value] = key
+	}
+	return reversedMap
+}
+
+var contactExcelHeader = []partner.ExcelHeader{
+	{Name: "*客户名称", Width: 30},
+	{Name: "助计名", Width: 30},
+	{Name: "*客户类型", Width: 30},
+	{Name: "*客户来源", Width: 30},
+	{Name: "所在省", Width: 30},
+	{Name: "所在市", Width: 30},
+	{Name: "所在区县", Width: 30},
+	{Name: "详细地址", Width: 30},
+	{Name: "招标关键字(以逗号分隔)", Width: 40},
+	{Name: "备注", Width: 30},
+	{Name: "*联系人姓名", Width: 30},
+	{Name: "*联系人性别", Width: 30},
+	{Name: "*联系人电话", Width: 30},
+	{Name: "联系人微信", Width: 30},
+	{Name: "联系人邮箱", Width: 30},
+	{Name: "联系人部门", Width: 30},
+	{Name: "*联系人职位", Width: 30},
+	{Name: "联系人办公地点", Width: 30},
+	{Name: "*关键决策人(是/否)", Width: 30},
+	{Name: "联系人备注", Width: 30},
+}
+
+var contactFailedExcelHeader = []partner.ExcelHeader{
+	{Name: "*客户名称", Width: 30},
+	{Name: "助计名", Width: 30},
+	{Name: "*客户类型", Width: 30},
+	{Name: "*客户来源", Width: 30},
+	{Name: "所在省", Width: 30},
+	{Name: "所在市", Width: 30},
+	{Name: "所在区县", Width: 30},
+	{Name: "详细地址", Width: 30},
+	{Name: "招标关键字(以逗号分隔)", Width: 40},
+	{Name: "备注", Width: 30},
+	{Name: "*联系人姓名", Width: 30},
+	{Name: "*联系人性别", Width: 30},
+	{Name: "*联系人电话", Width: 30},
+	{Name: "联系人微信", Width: 30},
+	{Name: "联系人邮箱", Width: 30},
+	{Name: "联系人部门", Width: 30},
+	{Name: "*联系人职位", Width: 30},
+	{Name: "联系人办公地点", Width: 30},
+	{Name: "*关键决策人(是/否)", Width: 30},
+	{Name: "联系人备注", Width: 30},
+	{Name: "失败原因", Width: 30},
+}
+
+func ExcelTemplate() (*excelizev2.File, error) {
+	tempData := [][]string{
+		{
+			"张三", "张三(备注)", "医院", "经销商", "北京市", "北京市", "朝阳区", "ffff", "20387,生物样本库", "备注",
+			"李四", "男", "18000000000", "weixin", "abc@163.com", "部门", "职务", "办公地点", "否", "联系人备注",
+		},
+	}
+	excel, err := partner.NewExcel(contactExcelHeader, tempData)
+	if err != nil {
+		return nil, err
+	}
+	return excel, nil
+}
+
+// Create 导入客户
+func (s *CustomerService) Import(ctx context.Context, req *contract.ExcelImportReq) (err error, faildCount int, faildData string) {
+	validErr := gvalid.CheckStruct(ctx, req, nil)
+	if validErr != nil {
+		return myerrors.TipsError(validErr.Current().Error()), 0, ""
+	}
+
+	// 下载文件
+	buf, err := partner.DownFile(req.ExcelUrl)
+	if err != nil {
+		return myerrors.TipsError(fmt.Sprintf("下载 excel 异常 %s", err.Error())), 0, ""
+	}
+
+	// 解析excel
+	excelData, err := s.parseExcel(buf)
+	if err != nil {
+		return myerrors.TipsError(fmt.Sprintf("解析 excel 异常 %s", err.Error())), 0, ""
+	}
+	//客户类型
+	idyMap, err := service.GetDictDataByType(ctx, "cust_idy")
+	if err != nil {
+		return err, 0, ""
+	}
+	idyMap = reverseMap(idyMap)
+	//客户来源
+	sourceMap, err := service.GetDictDataByType(ctx, "cust_source")
+	if err != nil {
+		return err, 0, ""
+	}
+	sourceMap = reverseMap(sourceMap)
+
+	//关键字
+	keywordsMap, err := service.GetDictDataByType(ctx, "customer_bidding_keywords")
+	if err != nil {
+		return err, 0, ""
+	}
+	keywordsMap = reverseMap(keywordsMap)
+	svc, err := server.NewDistrictService(ctx)
+	treeList, err := svc.GetProvincesList(0)
+	contactService, err := NewCustomerContactService(ctx)
+	if err != nil {
+		return err, 0, ""
+	}
+	CustomerService, err := NewCustomerService(ctx)
+	if err != nil {
+		return err, 0, ""
+	}
+	yesOrNoMap := map[string]string{
+		"是": "10",
+		"否": "20",
+	}
+	genderMap := map[string]string{
+		"男": "10",
+		"女": "20",
+	}
+	failedList := make([]*model.CustomerAddImport, 0)
+
+	e := s.Dao.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
+		for _, data := range excelData {
+			var province *base.ProvincesTree
+			var city *base.ProvincesTree
+			var region *base.ProvincesTree
+
+			keywords := []string{}
+			data.Keyword = gstr.Trim(data.Keyword)
+			if data.Keyword != "" && data.Keyword != "无" && data.Keyword != "-" {
+				keywords = strings.Split(data.Keyword, ",")
+
+				for _, keyword := range keywords {
+					if _, ok := keywordsMap[keyword]; !ok {
+						data.FailedReason = fmt.Sprintf("关键字 %s 不存在", keyword)
+
+						//return myerrors.TipsError(fmt.Sprintf("关键字 %s 不存在", keyword))
+					}
+				}
+				if data.FailedReason != "" {
+					failedList = append(failedList, data)
+					continue
+				}
+			}
+
+			if data.CustProvince == "" {
+				data.FailedReason = "客户省份不能为空"
+				failedList = append(failedList, data)
+				continue
+				//return myerrors.TipsError("客户省份不能为空")
+			}
+
+			for _, tree := range treeList {
+				if tree.DistName == data.CustProvince {
+					province = tree
+					if data.CustCity != "" && province.Children != nil {
+						for _, cityData := range province.Children {
+							if cityData.DistName == data.CustCity {
+								city = cityData
+								if data.CustRegion != "" && city.Children != nil {
+									for _, RegionData := range cityData.Children {
+										if RegionData.DistName == data.CustRegion {
+											region = RegionData
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			if (province == nil) || (city == nil && data.CustCity != "") || (region == nil && data.CustRegion != "") {
+				data.FailedReason = "省市区填写错误,请检查后重试"
+				failedList = append(failedList, data)
+				continue
+				//return myerrors.TipsError("客户省份不能为空")
+				//return myerrors.TipsError("省市区填写错误,请检查后重试")
+			}
+			if idyMap[data.CustIndustry] == "" {
+				data.FailedReason = "客户类型填写错误,请检查后重试"
+				failedList = append(failedList, data)
+				continue
+			}
+			if sourceMap[data.CustSource] == "" {
+				data.FailedReason = "客户来源填写错误,请检查后重试"
+				failedList = append(failedList, data)
+				continue
+			}
+
+			insertCustomer := &model.CustomerAddSeq{
+				CustName:       data.CustName,
+				AbbrName:       data.AbbrName,
+				CustAddress:    data.CustAddress,
+				CustProvinceId: province.Id,
+				CustProvince:   data.CustProvince,
+				CustCityId:     city.Id,
+				CustCity:       data.CustCity,
+				CustRegionId:   region.Id,
+				CustRegion:     data.CustRegion,
+				CustIndustry:   idyMap[data.CustIndustry],
+				CustSource:     sourceMap[data.CustSource],
+				CustDistCode:   province.Id,
+				Remark:         data.Remark,
+				Keyword:        keywords,
+			}
+
+			id, err := s.Create(insertCustomer)
+			if err != nil {
+				if err.Error() == "该客户信息已存在,不可重复添加" {
+					g.Log(fmt.Sprintf("[%s]%s", data.CustName, "该客户信息已存在,不可重复添加"))
+					g.Log().Error(err)
+					data.FailedReason = fmt.Sprintf("[%s]%s", data.CustName, "该客户信息已存在,不可重复添加")
+					failedList = append(failedList, data)
+					continue
+				}
+				g.Log().Error(err)
+
+				//return myerrors.TipsError(fmt.Sprintf("[%s]%s", data.CustName, "数据有误,请联系管理员"))
+			}
+			s.CreateDynamics("创建客户", req, id)
+			data.CuctName = gstr.Trim(data.CuctName)
+			if data.CuctName != "无" && data.CuctName != "" {
+				seq := model.CustCustomerContactSeq{
+					CustId:         int(id),
+					CuctName:       data.CuctName,
+					CuctGender:     genderMap[data.CuctGender],
+					Telephone:      data.Telephone,
+					Wechat:         data.Wechat,
+					Email:          data.Email,
+					Dept:           data.Dept,
+					Postion:        data.Postion,
+					OfficeLocation: data.OfficeLocation,
+					IsDecision:     yesOrNoMap[data.IsDecision],
+					Remark:         data.ContactRemark,
+				}
+				err = contactService.Create(&seq)
+				if err != nil {
+					data.FailedReason = "添加联系人 验证失败"
+					failedList = append(failedList, data)
+					continue
+					//return err
+				}
+
+				CustomerService.CreateDynamics("创建联系人", req, gconv.Int64(seq.CustId))
+			}
+
+		}
+		return nil
+	})
+
+	if e != nil {
+		return e, 0, ""
+	}
+	if len(failedList) > 0 {
+		f, err := ExportFailedData(failedList)
+		if err != nil {
+			return err, 0, ""
+		}
+		buf, err := f.WriteToBuffer()
+		if err != nil {
+			return err, 0, ""
+		}
+		faildData = base64.StdEncoding.EncodeToString(buf.Bytes())
+
+	}
+
+	return nil, len(failedList), faildData
+}
+
+// excel解构为数据
+func (s CustomerService) parseExcel(b []byte) ([]*model.CustomerAddImport, error) {
+	f, err := excelize.OpenReader(bytes.NewBuffer(b))
+	if err != nil {
+		return nil, err
+	}
+	sheet := "Sheet1"
+	rows := f.GetRows(sheet)
+	if err != nil {
+		return nil, err
+	}
+
+	// 客户名称 助计名 所在地区 详细地址 所在省 所在市 所在区县 客户行业 客户级别 客户来源 备注 销售名称 开票抬头 关联客户 姓名 性别 电话 微信 邮箱 部门 职位 办公地点 是否关键决策人 招标关键字
+	var saleTargets []*model.CustomerAddImport
+	for _, row := range rows[1:] {
+		temp := &model.CustomerAddImport{
+			CustName:       row[0],
+			AbbrName:       row[1],
+			CustIndustry:   row[2],
+			CustSource:     row[3],
+			CustProvince:   row[4],
+			CustCity:       row[5],
+			CustRegion:     row[6],
+			CustAddress:    row[7],
+			Keyword:        row[8],
+			Remark:         row[9],
+			CuctName:       row[10],
+			CuctGender:     row[11],
+			Telephone:      row[12],
+			Wechat:         row[13],
+			Email:          row[14],
+			Dept:           row[15],
+			Postion:        row[16],
+			OfficeLocation: row[17],
+			IsDecision:     row[18],
+			ContactRemark:  row[19],
+		}
+
+		saleTargets = append(saleTargets, temp)
+	}
+
+	return saleTargets, nil
+}
+func ExportFailedData(failedList []*model.CustomerAddImport) (*excelizev2.File, error) {
+	tempData := [][]string{}
+	for _, data := range failedList {
+		tempData = append(tempData, []string{
+			data.CustName, data.AbbrName, data.CustIndustry, data.CustSource, data.CustProvince, data.CustCity, data.CustRegion, data.CustAddress, data.Keyword, data.Remark,
+			data.CuctName, data.CuctGender, data.Telephone, data.Wechat, data.Email, data.Dept, data.Postion, data.OfficeLocation, data.IsDecision, data.ContactRemark, data.FailedReason,
+		})
+	}
+
+	excel, err := partner.NewExcel(contactFailedExcelHeader, tempData)
+	if err != nil {
+		return nil, err
+	}
+	return excel, nil
+}