From ae6883ce426727efca4facab973d814ae66c69c2 Mon Sep 17 00:00:00 2001
From: zhangqian <zhangqian@123.com>
Date: 星期五, 11 八月 2023 16:50:08 +0800
Subject: [PATCH] Merge branch 'master' of ssh://192.168.5.5:29418/aps/crm

---
 model/business.go                     |    8 
 model/plan.go                         |    7 
 pkg/ecode/code.go                     |    5 
 model/jsonTime.go                     |   52 +
 docs/swagger.yaml                     |  322 ++++++
 model/contact.go                      |   30 
 api/v1/quotation.go                   |    7 
 model/masterOrder.go                  |   22 
 model/collectionProjection.go         |  115 ++
 model/serviceFeeManage.go             |  352 +++---
 service/index.go                      |    1 
 service/collectionProjection.go       |   45 
 model/client.go                       |    4 
 api/v1/index.go                       |    2 
 model/salesReturn.go                  |   21 
 service/saleChance.go                 |   22 
 model/request/saleChance.go           |    5 
 model/saleChance.go                   |   72 
 api/v1/collectionProjection.go        |  138 ++
 model/quotation.go                    |   33 
 model/request/collectionProjection.go |   21 
 docs/docs.go                          |  483 ++++++++++
 docs/swagger.json                     |  483 ++++++++++
 model/response/response.go            |    5 
 model/followRecord.go                 |   43 
 model/model.go                        |   21 
 api/v1/saleChance.go                  |  451 ++++----
 router/saleChance.go                  |    5 
 model/salesRefund.go                  |   26 
 router/colletionProjection.go         |   19 
 model/index.go                        |    1 
 router/index.go                       |    3 
 api/v1/followRecord.go                |   27 
 model/request/quotation.go            |   21 
 34 files changed, 2,321 insertions(+), 551 deletions(-)

diff --git a/api/v1/collectionProjection.go b/api/v1/collectionProjection.go
new file mode 100644
index 0000000..2caa97b
--- /dev/null
+++ b/api/v1/collectionProjection.go
@@ -0,0 +1,138 @@
+package v1
+
+import (
+	"aps_crm/model"
+	"aps_crm/model/request"
+	"aps_crm/model/response"
+	"aps_crm/pkg/contextx"
+	"aps_crm/pkg/ecode"
+	"github.com/gin-gonic/gin"
+	"strconv"
+)
+
+type CollectionProjectionApi struct{}
+
+// Add
+//
+//	@Tags		CollectionProjection
+//	@Summary	娣诲姞鏀舵棰勬祴
+//	@Produce	application/json
+//	@Param		object	body		request.AddCollectionProjection	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/collectionProjection/add [post]
+func (cp *CollectionProjectionApi) Add(c *gin.Context) {
+	var params request.AddCollectionProjection
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	tmp := new(model.CollectionProjection)
+	tmp.EstimatedCollectionAmount = params.CollectionProjection.EstimatedCollectionAmount
+
+	if params.EstimatedCollectionTime == "" {
+		tmp.EstimatedCollectionDate = nil
+	} else {
+		tmp.EstimatedCollectionDate = &params.CollectionProjection.EstimatedCollectionTime
+	}
+
+	tmp.SaleChanceId = params.SaleChanceId
+
+	errCode := collectionProjectionService.AddCollectionProjection(tmp)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Delete
+//
+//	@Tags		CollectionProjection
+//	@Summary	鍒犻櫎鏀舵棰勬祴
+//	@Produce	application/json
+//	@Param		id	path		int	true	"鏌ヨ鍙傛暟"
+//	@Success	200	{object}	contextx.Response{}
+//	@Router		/api/collectionProjection/delete/{id} [delete]
+func (cp *CollectionProjectionApi) Delete(c *gin.Context) {
+	ctx, ok := contextx.NewContext(c, nil)
+	if !ok {
+		return
+	}
+
+	id, err := strconv.Atoi(c.Param("id"))
+	if err != nil {
+		ctx.Fail(ecode.InvalidParams)
+		return
+	}
+
+	errCode := collectionProjectionService.DeleteCollectionProjection(id)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Update
+//
+//	@Tags		CollectionProjection
+//	@Summary	鏇存柊鏀舵棰勬祴
+//	@Produce	application/json
+//	@Param		object	body		request.UpdateCollectionProjection	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/collectionProjection/update [put]
+func (cp *CollectionProjectionApi) Update(c *gin.Context) {
+	var params request.UpdateCollectionProjection
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	tmp := new(model.CollectionProjection)
+	tmp.EstimatedCollectionAmount = params.CollectionProjection.EstimatedCollectionAmount
+	tmp.Id = params.Id
+
+	if params.EstimatedCollectionTime == "" {
+		tmp.EstimatedCollectionDate = nil
+	} else {
+		tmp.EstimatedCollectionDate = &params.CollectionProjection.EstimatedCollectionTime
+	}
+
+	errCode := collectionProjectionService.UpdateCollectionProjection(tmp)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// List
+//
+//	@Tags		CollectionProjection
+//	@Summary	鑾峰彇鏀舵棰勬祴鍒楄〃
+//	@Produce	application/json
+//	@Param		object	query		request.GetCollectionProjectionList	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/collectionProjection/list [post]
+func (cp *CollectionProjectionApi) List(c *gin.Context) {
+	var params request.GetCollectionProjectionList
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	total, errCode, list := collectionProjectionService.GetCollectionProjectionList(params.Page, params.PageSize, params.SearchMap)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.OkWithDetailed(response.CollectionProjectionListResponse{
+		List:  list,
+		Count: int(total),
+	})
+}
diff --git a/api/v1/followRecord.go b/api/v1/followRecord.go
index e015177..0960dcf 100644
--- a/api/v1/followRecord.go
+++ b/api/v1/followRecord.go
@@ -6,6 +6,7 @@
 	"aps_crm/model/response"
 	"aps_crm/pkg/contextx"
 	"aps_crm/pkg/ecode"
+	"errors"
 	"github.com/gin-gonic/gin"
 	"time"
 )
@@ -172,27 +173,37 @@
 
 // checkTimeFormat
 // 妫�鏌ユ椂闂存牸寮�
-func checkTimeFormat(t string) (time.Time, error) {
+func checkTimeFormat(t string) (*model.CustomTime, error) {
 	if t == "" {
-		t = "1900-01-01T00:00:00+08:00"
+		return nil, nil
 	}
 
 	location, err := time.LoadLocation("Asia/Shanghai")
 	if err != nil {
-		return time.Time{}, err
+		return nil, err
 	}
 
-	tt, err := time.Parse("2006-01-02T15:04:05.000Z", t)
+	tt, err := time.Parse("2006-01-02", t)
 	if err == nil {
-		return tt.In(location), nil
+		ret := tt.In(location)
+		tmp := model.CustomTime(ret)
+		return &tmp, nil
 	}
 
-	tt, err = time.Parse("2006-01-02T15:04:05-07:00", t)
+	tt, err = time.Parse("2006-01-02 15:04:05", t)
 	if err == nil {
-		return tt.In(location), nil
+		ret := tt.In(location)
+		tmp := model.CustomTime(ret)
+		return &tmp, nil
 	}
 
-	return time.Time{}, err
+	//tt, err = time.Parse("2006-01-02T15:04:05-07:00", t)
+	//if err == nil {
+	//	ret := tt.In(location)
+	//	return &ret, nil
+	//}
+
+	return nil, errors.New("invalid time format")
 }
 
 // List
diff --git a/api/v1/index.go b/api/v1/index.go
index 00c8f70..b1d7c1e 100644
--- a/api/v1/index.go
+++ b/api/v1/index.go
@@ -65,6 +65,7 @@
 	VettingApi
 	SatisfactionApi
 	AssignApi
+	CollectionProjectionApi
 }
 
 var ApiGroup = new(Group)
@@ -130,4 +131,5 @@
 	quotationStatusService       = service.ServiceGroup.QuotationStatusService
 	currencyService              = service.ServiceGroup.CurrencyService
 	assignService                = service.ServiceGroup.AssignService
+	collectionProjectionService  = service.ServiceGroup.CollectionProjectionService
 )
diff --git a/api/v1/quotation.go b/api/v1/quotation.go
index 3a8dd0e..b436336 100644
--- a/api/v1/quotation.go
+++ b/api/v1/quotation.go
@@ -98,7 +98,6 @@
 	ctx.Ok()
 }
 
-
 // checkQuotationParams
 func checkQuotationParams(quotation request.Quotation) (int, model.Quotation) {
 	var errCode int
@@ -129,6 +128,8 @@
 		errCode = ecode.InvalidParams
 		return errCode, quotationModel
 	}
+
+	// 灏嗘椂闂村瓧绗﹁浆鎹负鏃堕棿绫诲瀷
 
 	quotationModel.ValidityDate = t
 	quotationModel.ClientId = quotation.ClientId
@@ -166,7 +167,7 @@
 	}
 
 	ctx.OkWithDetailed(response.QuotationResponse{
-		List: quotations,
+		List:  quotations,
 		Count: int(total),
 	})
-}
\ No newline at end of file
+}
diff --git a/api/v1/saleChance.go b/api/v1/saleChance.go
index feb94a0..245d7f8 100644
--- a/api/v1/saleChance.go
+++ b/api/v1/saleChance.go
@@ -1,212 +1,239 @@
-package v1
-
-import (
-	"aps_crm/model"
-	"aps_crm/model/request"
-	"aps_crm/model/response"
-	"aps_crm/pkg/contextx"
-	"aps_crm/pkg/ecode"
-	"github.com/gin-gonic/gin"
-	"strconv"
-)
-
-type SaleChanceApi struct{}
-
-// Add
-//
-//	@Tags		SaleChance
-//	@Summary	娣诲姞閿�鍞満浼�
-//	@Produce	application/json
-//	@Param		object	body		request.AddSaleChance	true	"鏌ヨ鍙傛暟"
-//	@Success	200		{object}	contextx.Response{}
-//	@Router		/api/saleChance/add [post]
-func (s *SaleChanceApi) Add(c *gin.Context) {
-	var params request.AddSaleChance
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	errCode, saleChance := checkSaleChanceParams(params.SaleChance)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	errCode = saleChanceService.AddSaleChance(&saleChance)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// Delete
-//
-//	@Tags		SaleChance
-//	@Summary	鍒犻櫎閿�鍞満浼�
-//	@Produce	application/json
-//	@Param		id	path		int	true	"鏌ヨ鍙傛暟"
-//	@Success	200	{object}	contextx.Response{}
-//	@Router		/api/saleChance/delete/{id} [delete]
-func (s *SaleChanceApi) Delete(c *gin.Context) {
-	ctx, ok := contextx.NewContext(c, nil)
-	if !ok {
-		return
-	}
-
-	id, _ := strconv.Atoi(c.Param("id"))
-	errCode := saleChanceService.DeleteSaleChance(id)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// Update
-//
-//	@Tags		SaleChance
-//	@Summary	鏇存柊閿�鍞満浼�
-//	@Produce	application/json
-//	@Param		object	body		request.UpdateSaleChance	true	"鏌ヨ鍙傛暟"
-//	@Success	200		{object}	contextx.Response{}
-//	@Router		/api/saleChance/update [put]
-func (s *SaleChanceApi) Update(c *gin.Context) {
-	var params request.UpdateSaleChance
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	// check id
-	if params.Id == 0 {
-		ctx.Fail(ecode.InvalidParams)
-		return
-	}
-
-	errCode, saleChance := checkSaleChanceParams(params.SaleChance)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	saleChance.Id = params.Id
-
-	errCode = saleChanceService.UpdateSaleChance(&saleChance)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.Ok()
-}
-
-// checkSaleChanceParams
-// 妫�鏌ラ攢鍞満浼氬弬鏁�
-func checkSaleChanceParams(saleChance request.SaleChance) (int, model.SaleChance) {
-	var errCode int
-	var sc model.SaleChance
-
-	//if saleChance.Name == "" {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-	//
-	//if saleChance.Number == "" {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-	//
-	//if saleChance.MemberId == 0 {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-	//
-	//if saleChance.Currency == 0 {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-	//
-	//if saleChance.ExpectedTime == "" {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-	//
-	//if saleChance.ProjectedAmount == 0 {
-	//	errCode = ecode.InvalidParams
-	//	return errCode, sc
-	//}
-
-	t, err := checkTimeFormat(saleChance.ExpectedTime)
-	if err != nil {
-		errCode = ecode.InvalidParams
-		return errCode, sc
-	}
-
-	sc.ExpectedTime = t
-
-	sc.Name = saleChance.Name
-	sc.Number = saleChance.Number
-	sc.ContactId = saleChance.ContactId
-	sc.ClientId = saleChance.ClientId
-	sc.SalesSourcesId = saleChance.SalesSourcesId
-	sc.SaleTypeId = saleChance.SaleTypeId
-	sc.SaleStageId = saleChance.SaleStageId
-	sc.MemberId = saleChance.MemberId
-	sc.RegularCustomersId = saleChance.RegularCustomersId
-	sc.Competitors = saleChance.Competitors
-	sc.PossibilitiesId = saleChance.Possibilities
-	sc.Budget = saleChance.Budget
-	sc.ProjectedAmount = saleChance.ProjectedAmount
-	sc.Currency = saleChance.Currency
-	sc.StatusId = saleChance.StatusId
-	sc.PainPoints = saleChance.PainPoints
-	sc.WhetherEstablished = saleChance.WhetherEstablished
-	sc.CapitalBudget = saleChance.CapitalBudget
-	sc.KeyMaker = saleChance.KeyMaker
-	sc.KeyFactors = saleChance.KeyFactors
-	sc.Process = saleChance.Process
-	sc.Solutions = saleChance.Solutions
-	sc.Advantages = saleChance.Advantages
-	sc.Disadvantages = saleChance.Disadvantages
-	sc.Opportunities = saleChance.Opportunities
-	sc.Threats = saleChance.Threats
-	sc.Remark = saleChance.Remark
-	sc.DetailAddress = saleChance.DetailAddress
-	sc.Address.RegionId = saleChance.Address.RegionId
-	sc.Address.CityId = saleChance.Address.CityId
-	sc.Address.CountryId = saleChance.Address.CountryId
-	sc.Address.ProvinceId = saleChance.Address.ProvinceId
-
-	return ecode.OK, sc
-}
-
-// List
-//
-//	@Tags		SaleChance
-//	@Summary	閿�鍞満浼氬垪琛�
-//	@Produce	application/json
-//	@Param		object	body		request.GetSaleChanceList	true	"鍙傛暟"
-// @Success	200		{object}	contextx.Response{data=response.SaleChanceResponse}
-//	@Router		/api/saleChance/list [post]
-func (con *SaleChanceApi) List(c *gin.Context) {
-	var params request.GetSaleChanceList
-	ctx, ok := contextx.NewContext(c, &params)
-	if !ok {
-		return
-	}
-
-	saleChances, total, errCode := saleChanceService.GetSaleChanceList(params.Page, params.PageSize, params.Keyword)
-	if errCode != ecode.OK {
-		ctx.Fail(errCode)
-		return
-	}
-
-	ctx.OkWithDetailed(response.SaleChanceResponse{
-		List:  saleChances,
-		Count: int(total),
-	})
-}
\ No newline at end of file
+package v1
+
+import (
+	"aps_crm/model"
+	"aps_crm/model/request"
+	"aps_crm/model/response"
+	"aps_crm/pkg/contextx"
+	"aps_crm/pkg/ecode"
+	"github.com/gin-gonic/gin"
+	"strconv"
+)
+
+type SaleChanceApi struct{}
+
+// Add
+//
+//	@Tags		SaleChance
+//	@Summary	娣诲姞閿�鍞満浼�
+//	@Produce	application/json
+//	@Param		object	body		request.AddSaleChance	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/saleChance/add [post]
+func (s *SaleChanceApi) Add(c *gin.Context) {
+	var params request.AddSaleChance
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	errCode, saleChance := checkSaleChanceParams(params.SaleChance)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	errCode = saleChanceService.AddSaleChance(&saleChance)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Delete
+//
+//	@Tags		SaleChance
+//	@Summary	鍒犻櫎閿�鍞満浼�
+//	@Produce	application/json
+//	@Param		id	path		int	true	"鏌ヨ鍙傛暟"
+//	@Success	200	{object}	contextx.Response{}
+//	@Router		/api/saleChance/delete/{id} [delete]
+func (s *SaleChanceApi) Delete(c *gin.Context) {
+	ctx, ok := contextx.NewContext(c, nil)
+	if !ok {
+		return
+	}
+
+	id, _ := strconv.Atoi(c.Param("id"))
+	errCode := saleChanceService.DeleteSaleChance(id)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// Update
+//
+//	@Tags		SaleChance
+//	@Summary	鏇存柊閿�鍞満浼�
+//	@Produce	application/json
+//	@Param		object	body		request.UpdateSaleChance	true	"鏌ヨ鍙傛暟"
+//	@Success	200		{object}	contextx.Response{}
+//	@Router		/api/saleChance/update [put]
+func (s *SaleChanceApi) Update(c *gin.Context) {
+	var params request.UpdateSaleChance
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	// check id
+	if params.Id == 0 {
+		ctx.Fail(ecode.InvalidParams)
+		return
+	}
+
+	errCode, saleChance := checkSaleChanceParams(params.SaleChance)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	saleChance.Id = params.Id
+
+	errCode = saleChanceService.UpdateSaleChance(&saleChance)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
+
+// checkSaleChanceParams
+// 妫�鏌ラ攢鍞満浼氬弬鏁�
+func checkSaleChanceParams(saleChance request.SaleChance) (int, model.SaleChance) {
+	var errCode int
+	var sc model.SaleChance
+
+	//if saleChance.Name == "" {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+	//
+	//if saleChance.Number == "" {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+	//
+	//if saleChance.MemberId == 0 {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+	//
+	//if saleChance.Currency == 0 {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+	//
+	//if saleChance.ExpectedTime == "" {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+	//
+	//if saleChance.ProjectedAmount == 0 {
+	//	errCode = ecode.InvalidParams
+	//	return errCode, sc
+	//}
+
+	t, err := checkTimeFormat(saleChance.ExpectedTime)
+	if err != nil {
+		errCode = ecode.InvalidParams
+		return errCode, sc
+	}
+
+	sc.ExpectedTime = t
+
+	sc.Name = saleChance.Name
+	sc.Number = saleChance.Number
+	sc.ContactId = saleChance.ContactId
+	sc.ClientId = saleChance.ClientId
+	sc.SalesSourcesId = saleChance.SalesSourcesId
+	sc.SaleTypeId = saleChance.SaleTypeId
+	sc.SaleStageId = saleChance.SaleStageId
+	sc.MemberId = saleChance.MemberId
+	sc.RegularCustomersId = saleChance.RegularCustomersId
+	sc.Competitors = saleChance.Competitors
+	sc.PossibilitiesId = saleChance.Possibilities
+	sc.Budget = saleChance.Budget
+	sc.ProjectedAmount = saleChance.ProjectedAmount
+	sc.Currency = saleChance.Currency
+	sc.StatusId = saleChance.StatusId
+	sc.PainPoints = saleChance.PainPoints
+	sc.WhetherEstablished = saleChance.WhetherEstablished
+	sc.CapitalBudget = saleChance.CapitalBudget
+	sc.KeyMaker = saleChance.KeyMaker
+	sc.KeyFactors = saleChance.KeyFactors
+	sc.Process = saleChance.Process
+	sc.Solutions = saleChance.Solutions
+	sc.Advantages = saleChance.Advantages
+	sc.Disadvantages = saleChance.Disadvantages
+	sc.Opportunities = saleChance.Opportunities
+	sc.Threats = saleChance.Threats
+	sc.Remark = saleChance.Remark
+	sc.DetailAddress = saleChance.DetailAddress
+	sc.Address.RegionId = saleChance.Address.RegionId
+	sc.Address.CityId = saleChance.Address.CityId
+	sc.Address.CountryId = saleChance.Address.CountryId
+	sc.Address.ProvinceId = saleChance.Address.ProvinceId
+
+	return ecode.OK, sc
+}
+
+// List
+//
+//	@Tags		SaleChance
+//	@Summary	閿�鍞満浼氬垪琛�
+//	@Produce	application/json
+//	@Param		object	body		request.GetSaleChanceList	true	"鍙傛暟"
+//
+// @Success	200		{object}	contextx.Response{data=response.SaleChanceResponse}
+//
+//	@Router		/api/saleChance/list [post]
+func (con *SaleChanceApi) List(c *gin.Context) {
+	var params request.GetSaleChanceList
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		return
+	}
+
+	saleChances, total, errCode := saleChanceService.GetSaleChanceList(params.Page, params.PageSize, params.Keyword)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.OkWithDetailed(response.SaleChanceResponse{
+		List:  saleChances,
+		Count: int(total),
+	})
+}
+
+// Push
+//
+//	@Tags		SaleChance
+//	@Summary	鎺ㄨ繘閿�鍞満浼�
+//	@Produce	application/json
+//	@Param		object	body		request.PushSaleChance  true	"鏌ヨ鍙傛暟"
+//	@Success	200	{object}	contextx.Response{}
+//	@Router		/api/saleChance/push [put]
+func (s *SaleChanceApi) Push(c *gin.Context) {
+	var params request.PushSaleChance
+	ctx, ok := contextx.NewContext(c, &params)
+	if !ok {
+		ctx.Fail(ecode.InvalidParams)
+		return
+	}
+
+	errCode := saleChanceService.PushSaleChance(params.Id, params.Step)
+	if errCode != ecode.OK {
+		ctx.Fail(errCode)
+		return
+	}
+
+	ctx.Ok()
+}
diff --git a/docs/docs.go b/docs/docs.go
index ab95e71..62f6386 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -1242,6 +1242,127 @@
                 }
             }
         },
+        "/api/collectionProjection/add": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "娣诲姞鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.AddCollectionProjection"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/delete/{id}": {
+            "delete": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鍒犻櫎鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/list": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鑾峰彇鏀舵棰勬祴鍒楄〃",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "椤电爜",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "姣忛〉澶у皬",
+                        "name": "pageSize",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/update": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鏇存柊鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.UpdateCollectionProjection"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/contact/add": {
             "post": {
                 "produces": [
@@ -6267,6 +6388,36 @@
                 }
             }
         },
+        "/api/saleChance/push": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SaleChance"
+                ],
+                "summary": "鎺ㄨ繘閿�鍞満浼�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.PushSaleChance"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/saleChance/update": {
             "put": {
                 "produces": [
@@ -8341,6 +8492,169 @@
                 }
             }
         },
+        "/api/serviceOrderStatus/add": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "娣诲姞鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.AddServiceOrderStatus"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/delete/{id}": {
+            "delete": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鍒犻櫎鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/list": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鑾峰彇鏈嶅姟鍗曠姸鎬佸垪琛�",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "name": "keyword",
+                        "in": "query"
+                    },
+                    {
+                        "enum": [
+                            ""
+                        ],
+                        "type": "string",
+                        "x-enum-varnames": [
+                            "ServiceOrderStatusKeywordCustomerName"
+                        ],
+                        "name": "keywordType",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "椤电爜",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "姣忛〉澶у皬",
+                        "name": "pageSize",
+                        "in": "query"
+                    },
+                    {
+                        "enum": [
+                            ""
+                        ],
+                        "type": "string",
+                        "x-enum-varnames": [
+                            "ServiceOrderStatusQueryClassExpireLessThen60Days"
+                        ],
+                        "name": "queryClass",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.ListResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/model.ServiceOrderStatus"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/update": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鏇存柊鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.UpdateServiceOrderStatus"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/serviceType/add": {
             "post": {
                 "produces": [
@@ -10008,6 +10322,24 @@
                 "ServiceOrderQueryClassExpireLessThen60Days"
             ]
         },
+        "constvar.ServiceOrderStatusKeywordType": {
+            "type": "string",
+            "enum": [
+                ""
+            ],
+            "x-enum-varnames": [
+                "ServiceOrderStatusKeywordCustomerName"
+            ]
+        },
+        "constvar.ServiceOrderStatusQueryClass": {
+            "type": "string",
+            "enum": [
+                ""
+            ],
+            "x-enum-varnames": [
+                "ServiceOrderStatusQueryClassExpireLessThen60Days"
+            ]
+        },
         "constvar.UserType": {
             "type": "integer",
             "enum": [
@@ -10286,6 +10618,42 @@
                     "type": "integer"
                 },
                 "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "model.CollectionProjection": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "description": "The date when the item was created\nexample: 2023-08-10 15:48:25",
+                    "type": "string"
+                },
+                "creator": {
+                    "type": "integer"
+                },
+                "deleted_at": {
+                    "description": "The date when the item was deleted\nexample: 2023-08-10 15:48:25",
+                    "type": "string"
+                },
+                "estimated_collection_amount": {
+                    "type": "number"
+                },
+                "estimated_collection_date": {
+                    "type": "string"
+                },
+                "id": {
+                    "description": "The ID of the item\nexample: 1",
+                    "type": "integer"
+                },
+                "modifier": {
+                    "type": "integer"
+                },
+                "sale_chance_id": {
+                    "type": "integer"
+                },
+                "updated_at": {
+                    "description": "The date when the item was last updated\nexample: 2023-08-10 15:48:25",
                     "type": "string"
                 }
             }
@@ -11022,6 +11390,12 @@
                 "number": {
                     "type": "string"
                 },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
+                },
                 "quotation_status_id": {
                     "type": "integer"
                 },
@@ -11184,6 +11558,12 @@
                 },
                 "client_id": {
                     "type": "integer"
+                },
+                "collection_projections": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.CollectionProjection"
+                    }
                 },
                 "competitors": {
                     "type": "string"
@@ -11660,6 +12040,9 @@
                 "clientId": {
                     "type": "integer"
                 },
+                "contact": {
+                    "$ref": "#/definitions/model.Contact"
+                },
                 "contactId": {
                     "type": "integer"
                 },
@@ -11862,11 +12245,11 @@
                     "type": "string"
                 },
                 "carFare": {
-                    "description": "浜ら�氳垂",
+                    "description": "浜ら�氳垂                                                                 // 浜ら�氳垂",
                     "type": "number"
                 },
                 "chargeAmount": {
-                    "description": "鏀惰垂閲戦",
+                    "description": "鏀惰垂閲戦                                                       // 鏀惰垂閲戦",
                     "type": "number"
                 },
                 "client": {
@@ -11959,6 +12342,9 @@
                     "description": "鏈嶅姟鍗曠紪鍙�",
                     "type": "string"
                 },
+                "serviceOrderStatus": {
+                    "$ref": "#/definitions/model.ServiceOrderStatus"
+                },
                 "serviceType": {
                     "$ref": "#/definitions/model.ServiceType"
                 },
@@ -11991,6 +12377,17 @@
                 "timeSpentId": {
                     "description": "鑺辫垂鏃堕棿",
                     "type": "integer"
+                }
+            }
+        },
+        "model.ServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
                 }
             }
         },
@@ -12368,6 +12765,23 @@
             "properties": {
                 "name": {
                     "type": "string"
+                }
+            }
+        },
+        "request.AddCollectionProjection": {
+            "type": "object",
+            "properties": {
+                "estimated_collection_amount": {
+                    "description": "棰勮鏀舵閲戦",
+                    "type": "number"
+                },
+                "estimated_collection_time": {
+                    "description": "棰勮鏀舵鏃堕棿",
+                    "type": "string"
+                },
+                "sale_chance_id": {
+                    "description": "閿�鍞満浼歩d",
+                    "type": "integer"
                 }
             }
         },
@@ -12832,6 +13246,12 @@
                 },
                 "number": {
                     "type": "string"
+                },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
                 },
                 "quotation_status_id": {
                     "type": "integer"
@@ -13601,6 +14021,17 @@
                 "timeSpentId": {
                     "description": "鑺辫垂鏃堕棿",
                     "type": "integer"
+                }
+            }
+        },
+        "request.AddServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
                 }
             }
         },
@@ -14462,6 +14893,21 @@
                 }
             }
         },
+        "request.PushSaleChance": {
+            "type": "object",
+            "required": [
+                "id",
+                "step"
+            ],
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "step": {
+                    "type": "integer"
+                }
+            }
+        },
         "request.PushSalesLeads": {
             "type": "object",
             "properties": {
@@ -14993,6 +15439,22 @@
                     "items": {
                         "$ref": "#/definitions/request.UpdateClientType"
                     }
+                }
+            }
+        },
+        "request.UpdateCollectionProjection": {
+            "type": "object",
+            "properties": {
+                "estimated_collection_amount": {
+                    "description": "棰勮鏀舵閲戦",
+                    "type": "number"
+                },
+                "estimated_collection_time": {
+                    "description": "棰勮鏀舵鏃堕棿",
+                    "type": "string"
+                },
+                "id": {
+                    "type": "integer"
                 }
             }
         },
@@ -15659,6 +16121,12 @@
                 },
                 "number": {
                     "type": "string"
+                },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
                 },
                 "quotation_status_id": {
                     "type": "integer"
@@ -16698,6 +17166,17 @@
                 }
             }
         },
+        "request.UpdateServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
         "request.UpdateServiceType": {
             "type": "object",
             "properties": {
diff --git a/docs/swagger.json b/docs/swagger.json
index ee2af69..f8fd45c 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -1230,6 +1230,127 @@
                 }
             }
         },
+        "/api/collectionProjection/add": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "娣诲姞鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.AddCollectionProjection"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/delete/{id}": {
+            "delete": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鍒犻櫎鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/list": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鑾峰彇鏀舵棰勬祴鍒楄〃",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "椤电爜",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "姣忛〉澶у皬",
+                        "name": "pageSize",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/collectionProjection/update": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "CollectionProjection"
+                ],
+                "summary": "鏇存柊鏀舵棰勬祴",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.UpdateCollectionProjection"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/contact/add": {
             "post": {
                 "produces": [
@@ -6255,6 +6376,36 @@
                 }
             }
         },
+        "/api/saleChance/push": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SaleChance"
+                ],
+                "summary": "鎺ㄨ繘閿�鍞満浼�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.PushSaleChance"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/saleChance/update": {
             "put": {
                 "produces": [
@@ -8329,6 +8480,169 @@
                 }
             }
         },
+        "/api/serviceOrderStatus/add": {
+            "post": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "娣诲姞鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.AddServiceOrderStatus"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/delete/{id}": {
+            "delete": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鍒犻櫎鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/list": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鑾峰彇鏈嶅姟鍗曠姸鎬佸垪琛�",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "name": "keyword",
+                        "in": "query"
+                    },
+                    {
+                        "enum": [
+                            ""
+                        ],
+                        "type": "string",
+                        "x-enum-varnames": [
+                            "ServiceOrderStatusKeywordCustomerName"
+                        ],
+                        "name": "keywordType",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "椤电爜",
+                        "name": "page",
+                        "in": "query"
+                    },
+                    {
+                        "type": "integer",
+                        "description": "姣忛〉澶у皬",
+                        "name": "pageSize",
+                        "in": "query"
+                    },
+                    {
+                        "enum": [
+                            ""
+                        ],
+                        "type": "string",
+                        "x-enum-varnames": [
+                            "ServiceOrderStatusQueryClassExpireLessThen60Days"
+                        ],
+                        "name": "queryClass",
+                        "in": "query"
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "allOf": [
+                                {
+                                    "$ref": "#/definitions/response.ListResponse"
+                                },
+                                {
+                                    "type": "object",
+                                    "properties": {
+                                        "data": {
+                                            "type": "array",
+                                            "items": {
+                                                "$ref": "#/definitions/model.ServiceOrderStatus"
+                                            }
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        },
+        "/api/serviceOrderStatus/update": {
+            "put": {
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "鏈嶅姟鍗曠姸鎬�"
+                ],
+                "summary": "鏇存柊鏈嶅姟鍗曠姸鎬�",
+                "parameters": [
+                    {
+                        "description": "鏌ヨ鍙傛暟",
+                        "name": "object",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.UpdateServiceOrderStatus"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/contextx.Response"
+                        }
+                    }
+                }
+            }
+        },
         "/api/serviceType/add": {
             "post": {
                 "produces": [
@@ -9996,6 +10310,24 @@
                 "ServiceOrderQueryClassExpireLessThen60Days"
             ]
         },
+        "constvar.ServiceOrderStatusKeywordType": {
+            "type": "string",
+            "enum": [
+                ""
+            ],
+            "x-enum-varnames": [
+                "ServiceOrderStatusKeywordCustomerName"
+            ]
+        },
+        "constvar.ServiceOrderStatusQueryClass": {
+            "type": "string",
+            "enum": [
+                ""
+            ],
+            "x-enum-varnames": [
+                "ServiceOrderStatusQueryClassExpireLessThen60Days"
+            ]
+        },
         "constvar.UserType": {
             "type": "integer",
             "enum": [
@@ -10274,6 +10606,42 @@
                     "type": "integer"
                 },
                 "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "model.CollectionProjection": {
+            "type": "object",
+            "properties": {
+                "created_at": {
+                    "description": "The date when the item was created\nexample: 2023-08-10 15:48:25",
+                    "type": "string"
+                },
+                "creator": {
+                    "type": "integer"
+                },
+                "deleted_at": {
+                    "description": "The date when the item was deleted\nexample: 2023-08-10 15:48:25",
+                    "type": "string"
+                },
+                "estimated_collection_amount": {
+                    "type": "number"
+                },
+                "estimated_collection_date": {
+                    "type": "string"
+                },
+                "id": {
+                    "description": "The ID of the item\nexample: 1",
+                    "type": "integer"
+                },
+                "modifier": {
+                    "type": "integer"
+                },
+                "sale_chance_id": {
+                    "type": "integer"
+                },
+                "updated_at": {
+                    "description": "The date when the item was last updated\nexample: 2023-08-10 15:48:25",
                     "type": "string"
                 }
             }
@@ -11010,6 +11378,12 @@
                 "number": {
                     "type": "string"
                 },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
+                },
                 "quotation_status_id": {
                     "type": "integer"
                 },
@@ -11172,6 +11546,12 @@
                 },
                 "client_id": {
                     "type": "integer"
+                },
+                "collection_projections": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.CollectionProjection"
+                    }
                 },
                 "competitors": {
                     "type": "string"
@@ -11648,6 +12028,9 @@
                 "clientId": {
                     "type": "integer"
                 },
+                "contact": {
+                    "$ref": "#/definitions/model.Contact"
+                },
                 "contactId": {
                     "type": "integer"
                 },
@@ -11850,11 +12233,11 @@
                     "type": "string"
                 },
                 "carFare": {
-                    "description": "浜ら�氳垂",
+                    "description": "浜ら�氳垂                                                                 // 浜ら�氳垂",
                     "type": "number"
                 },
                 "chargeAmount": {
-                    "description": "鏀惰垂閲戦",
+                    "description": "鏀惰垂閲戦                                                       // 鏀惰垂閲戦",
                     "type": "number"
                 },
                 "client": {
@@ -11947,6 +12330,9 @@
                     "description": "鏈嶅姟鍗曠紪鍙�",
                     "type": "string"
                 },
+                "serviceOrderStatus": {
+                    "$ref": "#/definitions/model.ServiceOrderStatus"
+                },
                 "serviceType": {
                     "$ref": "#/definitions/model.ServiceType"
                 },
@@ -11979,6 +12365,17 @@
                 "timeSpentId": {
                     "description": "鑺辫垂鏃堕棿",
                     "type": "integer"
+                }
+            }
+        },
+        "model.ServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
                 }
             }
         },
@@ -12356,6 +12753,23 @@
             "properties": {
                 "name": {
                     "type": "string"
+                }
+            }
+        },
+        "request.AddCollectionProjection": {
+            "type": "object",
+            "properties": {
+                "estimated_collection_amount": {
+                    "description": "棰勮鏀舵閲戦",
+                    "type": "number"
+                },
+                "estimated_collection_time": {
+                    "description": "棰勮鏀舵鏃堕棿",
+                    "type": "string"
+                },
+                "sale_chance_id": {
+                    "description": "閿�鍞満浼歩d",
+                    "type": "integer"
                 }
             }
         },
@@ -12820,6 +13234,12 @@
                 },
                 "number": {
                     "type": "string"
+                },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
                 },
                 "quotation_status_id": {
                     "type": "integer"
@@ -13589,6 +14009,17 @@
                 "timeSpentId": {
                     "description": "鑺辫垂鏃堕棿",
                     "type": "integer"
+                }
+            }
+        },
+        "request.AddServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
                 }
             }
         },
@@ -14450,6 +14881,21 @@
                 }
             }
         },
+        "request.PushSaleChance": {
+            "type": "object",
+            "required": [
+                "id",
+                "step"
+            ],
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "step": {
+                    "type": "integer"
+                }
+            }
+        },
         "request.PushSalesLeads": {
             "type": "object",
             "properties": {
@@ -14981,6 +15427,22 @@
                     "items": {
                         "$ref": "#/definitions/request.UpdateClientType"
                     }
+                }
+            }
+        },
+        "request.UpdateCollectionProjection": {
+            "type": "object",
+            "properties": {
+                "estimated_collection_amount": {
+                    "description": "棰勮鏀舵閲戦",
+                    "type": "number"
+                },
+                "estimated_collection_time": {
+                    "description": "棰勮鏀舵鏃堕棿",
+                    "type": "string"
+                },
+                "id": {
+                    "type": "integer"
                 }
             }
         },
@@ -15647,6 +16109,12 @@
                 },
                 "number": {
                     "type": "string"
+                },
+                "products": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.Product"
+                    }
                 },
                 "quotation_status_id": {
                     "type": "integer"
@@ -16686,6 +17154,17 @@
                 }
             }
         },
+        "request.UpdateServiceOrderStatus": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
         "request.UpdateServiceType": {
             "type": "object",
             "properties": {
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 2a99e5b..fb21e1c 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -264,6 +264,18 @@
     type: string
     x-enum-varnames:
     - ServiceOrderQueryClassExpireLessThen60Days
+  constvar.ServiceOrderStatusKeywordType:
+    enum:
+    - ""
+    type: string
+    x-enum-varnames:
+    - ServiceOrderStatusKeywordCustomerName
+  constvar.ServiceOrderStatusQueryClass:
+    enum:
+    - ""
+    type: string
+    x-enum-varnames:
+    - ServiceOrderStatusQueryClassExpireLessThen60Days
   constvar.UserType:
     enum:
     - 1
@@ -450,6 +462,39 @@
       id:
         type: integer
       name:
+        type: string
+    type: object
+  model.CollectionProjection:
+    properties:
+      created_at:
+        description: |-
+          The date when the item was created
+          example: 2023-08-10 15:48:25
+        type: string
+      creator:
+        type: integer
+      deleted_at:
+        description: |-
+          The date when the item was deleted
+          example: 2023-08-10 15:48:25
+        type: string
+      estimated_collection_amount:
+        type: number
+      estimated_collection_date:
+        type: string
+      id:
+        description: |-
+          The ID of the item
+          example: 1
+        type: integer
+      modifier:
+        type: integer
+      sale_chance_id:
+        type: integer
+      updated_at:
+        description: |-
+          The date when the item was last updated
+          example: 2023-08-10 15:48:25
         type: string
     type: object
   model.Contact:
@@ -933,6 +978,10 @@
         type: integer
       number:
         type: string
+      products:
+        items:
+          $ref: '#/definitions/model.Product'
+        type: array
       quotation_status_id:
         type: integer
       sale_chance:
@@ -1042,6 +1091,10 @@
         $ref: '#/definitions/model.Client'
       client_id:
         type: integer
+      collection_projections:
+        items:
+          $ref: '#/definitions/model.CollectionProjection'
+        type: array
       competitors:
         type: string
       contact:
@@ -1360,6 +1413,8 @@
         $ref: '#/definitions/model.Client'
       clientId:
         type: integer
+      contact:
+        $ref: '#/definitions/model.Contact'
       contactId:
         type: integer
       endTime:
@@ -1494,10 +1549,12 @@
         description: 棰勭害涓婇棬鏃堕棿
         type: string
       carFare:
-        description: 浜ら�氳垂
+        description: 浜ら�氳垂                                                                 //
+          浜ら�氳垂
         type: number
       chargeAmount:
-        description: 鏀惰垂閲戦
+        description: 鏀惰垂閲戦                                                       //
+          鏀惰垂閲戦
         type: number
       client:
         $ref: '#/definitions/model.Client'
@@ -1564,6 +1621,8 @@
       serviceNumber:
         description: 鏈嶅姟鍗曠紪鍙�
         type: string
+      serviceOrderStatus:
+        $ref: '#/definitions/model.ServiceOrderStatus'
       serviceType:
         $ref: '#/definitions/model.ServiceType'
       serviceTypeId:
@@ -1588,6 +1647,13 @@
       timeSpentId:
         description: 鑺辫垂鏃堕棿
         type: integer
+    type: object
+  model.ServiceOrderStatus:
+    properties:
+      id:
+        type: integer
+      name:
+        type: string
     type: object
   model.ServiceType:
     properties:
@@ -1843,6 +1909,18 @@
         type: string
     required:
     - name
+    type: object
+  request.AddCollectionProjection:
+    properties:
+      estimated_collection_amount:
+        description: 棰勮鏀舵閲戦
+        type: number
+      estimated_collection_time:
+        description: 棰勮鏀舵鏃堕棿
+        type: string
+      sale_chance_id:
+        description: 閿�鍞満浼歩d
+        type: integer
     type: object
   request.AddContact:
     properties:
@@ -2152,6 +2230,10 @@
         type: integer
       number:
         type: string
+      products:
+        items:
+          $ref: '#/definitions/model.Product'
+        type: array
       quotation_status_id:
         type: integer
       sale_chance_id:
@@ -2678,6 +2760,13 @@
       timeSpentId:
         description: 鑺辫垂鏃堕棿
         type: integer
+    type: object
+  request.AddServiceOrderStatus:
+    properties:
+      id:
+        type: integer
+      name:
+        type: string
     type: object
   request.AddServiceType:
     properties:
@@ -3276,6 +3365,16 @@
       subOrderId:
         type: integer
     type: object
+  request.PushSaleChance:
+    properties:
+      id:
+        type: integer
+      step:
+        type: integer
+    required:
+    - id
+    - step
+    type: object
   request.PushSalesLeads:
     properties:
       id:
@@ -3634,6 +3733,17 @@
         type: array
     required:
     - client_types
+    type: object
+  request.UpdateCollectionProjection:
+    properties:
+      estimated_collection_amount:
+        description: 棰勮鏀舵閲戦
+        type: number
+      estimated_collection_time:
+        description: 棰勮鏀舵鏃堕棿
+        type: string
+      id:
+        type: integer
     type: object
   request.UpdateContact:
     properties:
@@ -4080,6 +4190,10 @@
         type: integer
       number:
         type: string
+      products:
+        items:
+          $ref: '#/definitions/model.Product'
+        type: array
       quotation_status_id:
         type: integer
       sale_chance_id:
@@ -4786,6 +4900,13 @@
       timeSpentId:
         description: 鑺辫垂鏃堕棿
         type: integer
+    type: object
+  request.UpdateServiceOrderStatus:
+    properties:
+      id:
+        type: integer
+      name:
+        type: string
     type: object
   request.UpdateServiceType:
     properties:
@@ -6248,6 +6369,83 @@
       summary: 鏇存柊瀹㈡埛绫诲瀷
       tags:
       - ClientType
+  /api/collectionProjection/add:
+    post:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: body
+        name: object
+        required: true
+        schema:
+          $ref: '#/definitions/request.AddCollectionProjection'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 娣诲姞鏀舵棰勬祴
+      tags:
+      - CollectionProjection
+  /api/collectionProjection/delete/{id}:
+    delete:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: path
+        name: id
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鍒犻櫎鏀舵棰勬祴
+      tags:
+      - CollectionProjection
+  /api/collectionProjection/list:
+    post:
+      parameters:
+      - description: 椤电爜
+        in: query
+        name: page
+        type: integer
+      - description: 姣忛〉澶у皬
+        in: query
+        name: pageSize
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鑾峰彇鏀舵棰勬祴鍒楄〃
+      tags:
+      - CollectionProjection
+  /api/collectionProjection/update:
+    put:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: body
+        name: object
+        required: true
+        schema:
+          $ref: '#/definitions/request.UpdateCollectionProjection'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鏇存柊鏀舵棰勬祴
+      tags:
+      - CollectionProjection
   /api/contact/add:
     post:
       parameters:
@@ -9340,6 +9538,25 @@
       summary: 閿�鍞満浼氬垪琛�
       tags:
       - SaleChance
+  /api/saleChance/push:
+    put:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: body
+        name: object
+        required: true
+        schema:
+          $ref: '#/definitions/request.PushSaleChance'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鎺ㄨ繘閿�鍞満浼�
+      tags:
+      - SaleChance
   /api/saleChance/update:
     put:
       parameters:
@@ -10615,6 +10832,107 @@
       summary: 鏇存柊鏈嶅姟鍗�
       tags:
       - 鏈嶅姟鍗曠鐞�
+  /api/serviceOrderStatus/add:
+    post:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: body
+        name: object
+        required: true
+        schema:
+          $ref: '#/definitions/request.AddServiceOrderStatus'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 娣诲姞鏈嶅姟鍗曠姸鎬�
+      tags:
+      - 鏈嶅姟鍗曠姸鎬�
+  /api/serviceOrderStatus/delete/{id}:
+    delete:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: path
+        name: id
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鍒犻櫎鏈嶅姟鍗曠姸鎬�
+      tags:
+      - 鏈嶅姟鍗曠姸鎬�
+  /api/serviceOrderStatus/list:
+    get:
+      parameters:
+      - in: query
+        name: keyword
+        type: string
+      - enum:
+        - ""
+        in: query
+        name: keywordType
+        type: string
+        x-enum-varnames:
+        - ServiceOrderStatusKeywordCustomerName
+      - description: 椤电爜
+        in: query
+        name: page
+        type: integer
+      - description: 姣忛〉澶у皬
+        in: query
+        name: pageSize
+        type: integer
+      - enum:
+        - ""
+        in: query
+        name: queryClass
+        type: string
+        x-enum-varnames:
+        - ServiceOrderStatusQueryClassExpireLessThen60Days
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            allOf:
+            - $ref: '#/definitions/response.ListResponse'
+            - properties:
+                data:
+                  items:
+                    $ref: '#/definitions/model.ServiceOrderStatus'
+                  type: array
+              type: object
+      summary: 鑾峰彇鏈嶅姟鍗曠姸鎬佸垪琛�
+      tags:
+      - 鏈嶅姟鍗曠姸鎬�
+  /api/serviceOrderStatus/update:
+    put:
+      parameters:
+      - description: 鏌ヨ鍙傛暟
+        in: body
+        name: object
+        required: true
+        schema:
+          $ref: '#/definitions/request.UpdateServiceOrderStatus'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/contextx.Response'
+      summary: 鏇存柊鏈嶅姟鍗曠姸鎬�
+      tags:
+      - 鏈嶅姟鍗曠姸鎬�
   /api/serviceType/add:
     post:
       parameters:
diff --git a/model/business.go b/model/business.go
index 9420a5c..fe1625f 100644
--- a/model/business.go
+++ b/model/business.go
@@ -1,12 +1,10 @@
 package model
 
-import "time"
-
 type (
 	Business struct {
-		Representative      string    `json:"representative" gorm:"column:representative;type:varchar(255);comment:娉曚汉浠h〃"`
-		RegistrationTime    time.Time `json:"registration_time" gorm:"column:registration_time;type:datetime;default:1970-01-01 08:00:00;comment:娉ㄥ唽鏃堕棿"`
-		RegisteredCapitalId int       `json:"registered_capital_id" gorm:"column:registered_capital_id;type:int(11);comment:娉ㄥ唽璧勯噾"`
+		Representative      string      `json:"representative" gorm:"column:representative;type:varchar(255);comment:娉曚汉浠h〃"`
+		RegistrationTime    *CustomTime `json:"registration_time" gorm:"column:registration_time;type:datetime;default:1970-01-01 08:00:00;comment:娉ㄥ唽鏃堕棿"`
+		RegisteredCapitalId int         `json:"registered_capital_id" gorm:"column:registered_capital_id;type:int(11);comment:娉ㄥ唽璧勯噾"`
 		RegisteredCapital   RegisteredCapital
 		IndustryId          int `json:"industry_id" gorm:"column:industry_id;type:int(11);comment:鎵�灞炶涓�"`
 		Industry            Industry
diff --git a/model/client.go b/model/client.go
index 7cec5b9..f7470f6 100644
--- a/model/client.go
+++ b/model/client.go
@@ -26,8 +26,8 @@
 		ServiceMemberId   int            `json:"service_member_id" gorm:"column:service_member_id;type:int(11);comment:鏈嶅姟璐熻矗浜篒D"`
 		DetailAddress     string         `json:"detail_address" gorm:"column:detail_address;type:varchar(255);comment:璇︾粏鍦板潃"`
 		Remark            string         `json:"remark" gorm:"column:remark;type:varchar(255);comment:澶囨敞"`
-		NextVisitTime     time.Time      `json:"next_visit_time" gorm:"column:next_visit_time;type:datetime;comment:涓嬫鍥炶鏃堕棿"`
-		LatestServiceTime time.Time      `json:"latest_service_time" gorm:"column:latest_service_time;type:datetime;comment:鏈�鏅氭湇鍔℃椂闂�"`
+		NextVisitTime     *CustomTime    `json:"next_visit_time" gorm:"column:next_visit_time;type:datetime;comment:涓嬫鍥炶鏃堕棿"`
+		LatestServiceTime *CustomTime    `json:"latest_service_time" gorm:"column:latest_service_time;type:datetime;comment:鏈�鏅氭湇鍔℃椂闂�"`
 		FollowRecord      []FollowRecord `json:"follow_record" gorm:"foreignKey:ClientId"`
 		Address
 		Business
diff --git a/model/collectionProjection.go b/model/collectionProjection.go
new file mode 100644
index 0000000..de10709
--- /dev/null
+++ b/model/collectionProjection.go
@@ -0,0 +1,115 @@
+package model
+
+import (
+	"aps_crm/pkg/mysqlx"
+	"gorm.io/gorm"
+)
+
+type (
+	CollectionProjection struct {
+		Id                        int     `json:"id" gorm:"column:id;primaryKey;autoIncrement;not null"`
+		SaleChanceId              int     `json:"sale_chance_id" gorm:"column:sale_chance_id;type:int(11);comment:閿�鍞満浼歩d"`
+		Creator                   int     `json:"creator" gorm:"column:creator;type:int(11);comment:鍒涘缓浜�"`
+		Modifier                  int     `json:"modifier" gorm:"column:modifier;type:int(11);comment:淇敼浜�"`
+		EstimatedCollectionDate   *string `json:"estimated_collection_date" gorm:"column:estimated_collection_date;type:datetime;comment:棰勮鏀舵鏃ユ湡"`
+		EstimatedCollectionAmount float64 `json:"estimated_collection_amount" gorm:"column:estimated_collection_amount;type:decimal(10,2);comment:棰勮鏀舵閲戦"`
+		gormModel
+	}
+
+	CollectionProjectionSearch struct {
+		CollectionProjection
+
+		Orm       *gorm.DB
+		SearchMap map[string]interface{}
+		OrderBy   string
+		PageNum   int
+		PageSize  int
+	}
+)
+
+func (CollectionProjection) TableName() string {
+	return "collection_projection"
+}
+
+func NewCollectionProjectionSearch() *CollectionProjectionSearch {
+	return &CollectionProjectionSearch{
+		Orm: mysqlx.GetDB(),
+	}
+}
+
+func (slf *CollectionProjectionSearch) build() *gorm.DB {
+	var db = slf.Orm.Model(&CollectionProjection{})
+	if slf.Id != 0 {
+		db = db.Where("id = ?", slf.Id)
+	}
+
+	if len(slf.SearchMap) > 0 {
+		for key, value := range slf.SearchMap {
+			switch v := value.(type) {
+			case string:
+				if key == "estimated_collection_date" {
+					db = db.Where(key+" = ?", v)
+				}
+			case int:
+				if key == "client_id" {
+					db = db.Where(key+" = ?", v)
+				}
+			}
+		}
+	}
+
+	return db
+}
+
+func (slf *CollectionProjectionSearch) Create(record *CollectionProjection) (err error) {
+	var db = slf.build()
+	err = db.Create(record).Error
+	return
+}
+
+func (slf *CollectionProjectionSearch) Update(record *CollectionProjection) (err error) {
+	var db = slf.build()
+	err = db.Updates(record).Error
+	return
+}
+
+func (slf *CollectionProjectionSearch) Delete() (err error) {
+	var db = slf.build()
+	err = db.Delete(&CollectionProjection{}).Error
+	return
+}
+
+func (slf *CollectionProjectionSearch) Find() (int64, error, []*CollectionProjection) {
+	var db = slf.build()
+	var records = make([]*CollectionProjection, 0)
+	var total int64
+	if err := db.Count(&total).Error; err != nil {
+		return total, err, records
+	}
+	if slf.PageNum > 0 && slf.PageSize > 0 {
+		db = db.Offset((slf.PageNum - 1) * slf.PageSize).Limit(slf.PageSize)
+	}
+
+	err := db.Find(&records).Error
+	return total, err, records
+}
+
+func (slf *CollectionProjectionSearch) SetID(id int) *CollectionProjectionSearch {
+	slf.Id = id
+	return slf
+}
+
+func (slf *CollectionProjectionSearch) SetSaleChanceId(saleChanceId int) *CollectionProjectionSearch {
+	slf.SaleChanceId = saleChanceId
+	return slf
+}
+
+func (slf *CollectionProjectionSearch) SetPage(page, size int) *CollectionProjectionSearch {
+	slf.PageNum, slf.PageSize = page, size
+	return slf
+}
+
+func (slf *CollectionProjectionSearch) SetSearchMap(data map[string]interface{}) *CollectionProjectionSearch {
+	slf.SearchMap = data
+	return slf
+}
diff --git a/model/contact.go b/model/contact.go
index 612fcba..a955791 100644
--- a/model/contact.go
+++ b/model/contact.go
@@ -3,24 +3,23 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	Contact struct {
-		Id       int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		Name     string    `json:"name" gorm:"column:name;type:varchar(255);comment:鑱旂郴浜哄鍚�"`
-		Number   string    `json:"number" gorm:"column:number;type:varchar(255);comment:鑱旂郴浜虹紪鍙�"`
-		ClientId int       `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
-		Client   Client    `json:"-" gorm:"foreignKey:ClientId"`
-		Position string    `json:"position" gorm:"column:position;type:varchar(255);comment:鑱屼綅"`
-		Phone    string    `json:"phone" gorm:"column:phone;type:varchar(255);comment:鐢佃瘽"`
-		MemberId int       `json:"member_id" gorm:"column:member_id;type:int(11);comment:璐熻矗浜篒D"`
-		IsFirst  bool      `json:"is_first" gorm:"column:is_first;type:tinyint(1);comment:鏄惁棣栬鑱旂郴浜�"`
-		Wechat   string    `json:"wechat" gorm:"column:wechat;type:varchar(255);comment:寰俊"`
-		Birthday time.Time `json:"birthday" gorm:"column:birthday;type:datetime;comment:鐢熸棩"`
-		Email    string    `json:"email" gorm:"column:email;type:varchar(255);comment:閭"`
-		Desc     string    `json:"desc" gorm:"column:desc;type:varchar(255);comment:澶囨敞"`
+		Id       int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		Name     string      `json:"name" gorm:"column:name;type:varchar(255);comment:鑱旂郴浜哄鍚�"`
+		Number   string      `json:"number" gorm:"column:number;type:varchar(255);comment:鑱旂郴浜虹紪鍙�"`
+		ClientId int         `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
+		Client   Client      `json:"-" gorm:"foreignKey:ClientId"`
+		Position string      `json:"position" gorm:"column:position;type:varchar(255);comment:鑱屼綅"`
+		Phone    string      `json:"phone" gorm:"column:phone;type:varchar(255);comment:鐢佃瘽"`
+		MemberId int         `json:"member_id" gorm:"column:member_id;type:int(11);comment:璐熻矗浜篒D"`
+		IsFirst  bool        `json:"is_first" gorm:"column:is_first;type:tinyint(1);comment:鏄惁棣栬鑱旂郴浜�"`
+		Wechat   string      `json:"wechat" gorm:"column:wechat;type:varchar(255);comment:寰俊"`
+		Birthday *CustomTime `json:"birthday" gorm:"column:birthday;type:datetime;comment:鐢熸棩"`
+		Email    string      `json:"email" gorm:"column:email;type:varchar(255);comment:閭"`
+		Desc     string      `json:"desc" gorm:"column:desc;type:varchar(255);comment:澶囨敞"`
 		Address
 		gorm.Model `json:"-"`
 	}
@@ -93,6 +92,9 @@
 					}
 				}
 			case int:
+				if key == "client_id" {
+					db = db.Where("client_id = ?", v)
+				}
 			}
 		}
 	}
diff --git a/model/followRecord.go b/model/followRecord.go
index 7a8807b..b12a6da 100644
--- a/model/followRecord.go
+++ b/model/followRecord.go
@@ -3,29 +3,28 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	FollowRecord struct {
-		Id                   int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		ClientId             int       `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛id"`
-		ClientStatusId       int       `json:"client_status_id" gorm:"column:client_status_id;type:int(11);comment:瀹㈡埛鐘舵�乮d"`
-		MemberId             int       `json:"member_id" gorm:"column:member_id;type:int(11);comment:璺熻繘浜篿d"`
-		Member               User      `json:"member" gorm:"foreignKey:MemberId"`
-		Number               string    `json:"number" gorm:"column:number;type:varchar(255);comment:璺熻繘缂栧彿"`
-		ContactId            int       `json:"contact_id" gorm:"column:contact_id;type:int(11);comment:鑱旂郴浜篿d"`
-		Topic                string    `json:"topic" gorm:"column:topic;type:varchar(255);comment:璺熻繘涓婚"`
-		Record               string    `json:"record" gorm:"column:record;type:MEDIUMTEXT;comment:璺熻繘璁板綍"`
-		SaleChanceId         int       `json:"sale_chance_id" gorm:"column:sale_chance_id;type:int(11);comment:閿�鍞満浼歩d"`
-		SalesLeadsId         int       `json:"sales_leads_id" gorm:"column:sales_leads_id;type:int(11);comment:閿�鍞嚎绱d"`
-		ContactInformationId int       `json:"contact_information_id" gorm:"column:contact_information_id;type:int(11);comment:鑱旂郴鏂瑰紡id"`
-		FollowTime           time.Time `json:"follow_time" gorm:"column:follow_time;type:datetime;comment:璺熻繘鏃堕棿"`
-		NextFollowTime       time.Time `json:"next_follow_time" gorm:"column:next_follow_time;type:datetime;comment:涓嬫璺熻繘鏃堕棿"`
-		Purpose              string    `json:"purpose" gorm:"column:purpose;type:varchar(255);comment:璺熻繘鐩殑"`
-		Content              string    `json:"content" gorm:"column:content;type:MEDIUMTEXT;comment:璺熻繘鍐呭"`
-		Client               Client    `json:"client" gorm:"foreignKey:ClientId"`
-		Contact              Contact   `json:"contact" gorm:"foreignKey:ContactId"`
+		Id                   int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		ClientId             int         `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛id"`
+		ClientStatusId       int         `json:"client_status_id" gorm:"column:client_status_id;type:int(11);comment:瀹㈡埛鐘舵�乮d"`
+		MemberId             int         `json:"member_id" gorm:"column:member_id;type:int(11);comment:璺熻繘浜篿d"`
+		Member               User        `json:"member" gorm:"foreignKey:MemberId"`
+		Number               string      `json:"number" gorm:"column:number;type:varchar(255);comment:璺熻繘缂栧彿"`
+		ContactId            int         `json:"contact_id" gorm:"column:contact_id;type:int(11);comment:鑱旂郴浜篿d"`
+		Topic                string      `json:"topic" gorm:"column:topic;type:varchar(255);comment:璺熻繘涓婚"`
+		Record               string      `json:"record" gorm:"column:record;type:MEDIUMTEXT;comment:璺熻繘璁板綍"`
+		SaleChanceId         int         `json:"sale_chance_id" gorm:"column:sale_chance_id;type:int(11);comment:閿�鍞満浼歩d"`
+		SalesLeadsId         int         `json:"sales_leads_id" gorm:"column:sales_leads_id;type:int(11);comment:閿�鍞嚎绱d"`
+		ContactInformationId int         `json:"contact_information_id" gorm:"column:contact_information_id;type:int(11);comment:鑱旂郴鏂瑰紡id"`
+		FollowTime           *CustomTime `json:"follow_time" gorm:"column:follow_time;type:datetime;comment:璺熻繘鏃堕棿"`
+		NextFollowTime       *CustomTime `json:"next_follow_time" gorm:"column:next_follow_time;type:datetime;comment:涓嬫璺熻繘鏃堕棿"`
+		Purpose              string      `json:"purpose" gorm:"column:purpose;type:varchar(255);comment:璺熻繘鐩殑"`
+		Content              string      `json:"content" gorm:"column:content;type:MEDIUMTEXT;comment:璺熻繘鍐呭"`
+		Client               Client      `json:"client" gorm:"foreignKey:ClientId"`
+		Contact              Contact     `json:"contact" gorm:"foreignKey:ContactId"`
 		gorm.Model           `json:"-"`
 	}
 
@@ -94,6 +93,12 @@
 				if key == "client_status" {
 					db = db.Joins("Client").Joins("Client.ClientStatus").Where("Client__ClientStatus.name LIKE ?", "%"+v+"%")
 				}
+			case int:
+			case int64:
+			case float64:
+				if key == "client_id" || key == "contact_id" || key == "sales_leads_id" || key == "sale_chance_id" {
+					db = db.Where(key+" = ?", v)
+				}
 			}
 		}
 	}
diff --git a/model/index.go b/model/index.go
index 7a90d35..0da8fa8 100644
--- a/model/index.go
+++ b/model/index.go
@@ -83,6 +83,7 @@
 		Repository{},
 		QuotationStatus{},
 		Currency{},
+		CollectionProjection{},
 	)
 	return err
 }
diff --git a/model/jsonTime.go b/model/jsonTime.go
new file mode 100644
index 0000000..64bf399
--- /dev/null
+++ b/model/jsonTime.go
@@ -0,0 +1,52 @@
+package model
+
+import (
+	"database/sql/driver"
+	"encoding/json"
+	"fmt"
+	"time"
+)
+
+type CustomTime time.Time
+
+const ctLayout = "2006-01-02 15:04:05" // 鎯宠鐨勬椂闂存牸寮�
+
+func (ct *CustomTime) MarshalJSON() ([]byte, error) {
+	return json.Marshal(time.Time(*ct).Format(ctLayout))
+}
+
+func (ct *CustomTime) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+
+	t, err := time.Parse(ctLayout, s)
+	if err != nil {
+		return err
+	}
+
+	*ct = CustomTime(t)
+	return nil
+}
+
+// Scan 灏嗘暟鎹簱鍊兼壂鎻忓埌Go涓殑CustomTime
+func (ct *CustomTime) Scan(value interface{}) error {
+	if value == nil {
+		*ct = CustomTime(time.Time{})
+		return nil
+	}
+	if t, ok := value.(time.Time); ok {
+		*ct = CustomTime(t)
+		return nil
+	}
+	return fmt.Errorf("can't scan %T into CustomTime", value)
+}
+
+// Value 灏咰ustomTime鐨勫�艰浆鎹负鏁版嵁搴撳��
+func (ct CustomTime) Value() (driver.Value, error) {
+	if time.Time(ct).IsZero() {
+		return nil, nil
+	}
+	return time.Time(ct), nil
+}
diff --git a/model/masterOrder.go b/model/masterOrder.go
index 71f053e..56550f8 100644
--- a/model/masterOrder.go
+++ b/model/masterOrder.go
@@ -3,20 +3,19 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	// MasterOrder 閿�鍞�诲崟
 	MasterOrder struct {
-		Id         int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		Number     string    `json:"number" gorm:"column:number;type:varchar(255);comment:閿�鍞�诲崟鍙�"`
-		ClientId   int       `json:"client_id" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
-		Client     Client    `json:"client" gorm:"foreignKey:ClientId"`
-		MemberId   int       `json:"member_id" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
-		StartTime  time.Time `json:"start_time" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
-		EndTime    time.Time `json:"end_time" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
-		Money      float64   `json:"money" gorm:"column:money;type:decimal(10,2);comment:鎬婚噾棰�"`
+		Id         int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		Number     string      `json:"number" gorm:"column:number;type:varchar(255);comment:閿�鍞�诲崟鍙�"`
+		ClientId   int         `json:"client_id" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
+		Client     Client      `json:"client" gorm:"foreignKey:ClientId"`
+		MemberId   int         `json:"member_id" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
+		StartTime  *CustomTime `json:"start_time" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
+		EndTime    *CustomTime `json:"end_time" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
+		Money      float64     `json:"money" gorm:"column:money;type:decimal(10,2);comment:鎬婚噾棰�"`
 		gorm.Model `json:"-"`
 	}
 
@@ -24,12 +23,11 @@
 	MasterOrderSearch struct {
 		MasterOrder
 
-				Orm      *gorm.DB
+		Orm      *gorm.DB
 		Keyword  string
 		OrderBy  string
 		PageNum  int
 		PageSize int
-
 	}
 )
 
@@ -114,4 +112,4 @@
 func (slf *MasterOrderSearch) SetOrder(order string) *MasterOrderSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/model.go b/model/model.go
new file mode 100644
index 0000000..5963906
--- /dev/null
+++ b/model/model.go
@@ -0,0 +1,21 @@
+package model
+
+import "time"
+
+// MyModel definitions from gorm.Model
+//
+// swagger:model
+type gormModel struct {
+	// The ID of the item
+	// example: 1
+	ID uint `json:"id"`
+	// The date when the item was created
+	// example: 2023-08-10 15:48:25
+	CreatedAt time.Time `json:"created_at"`
+	// The date when the item was last updated
+	// example: 2023-08-10 15:48:25
+	UpdatedAt time.Time `json:"updated_at"`
+	// The date when the item was deleted
+	// example: 2023-08-10 15:48:25
+	DeletedAt *time.Time `json:"deleted_at,omitempty"`
+}
diff --git a/model/plan.go b/model/plan.go
index a8ccdda..6441c30 100644
--- a/model/plan.go
+++ b/model/plan.go
@@ -3,7 +3,6 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
@@ -16,8 +15,8 @@
 		SubOrder       SubOrder     `json:"subOrder" gorm:"foreignKey:SubOrderId"`
 		SalesDetailsId int          `json:"salesDetailsId" gorm:"column:sales_details_id;type:int;comment:閿�鍞槑缁唅d"`
 		SalesDetails   SalesDetails `json:"salesDetails" gorm:"foreignKey:SalesDetailsId"`
-		StartTime      time.Time    `json:"startTime" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
-		EndTime        time.Time    `json:"endTime" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
+		StartTime      *CustomTime  `json:"startTime" gorm:"column:start_time;type:datetime;comment:寮�濮嬫椂闂�"`
+		EndTime        *CustomTime  `json:"endTime" gorm:"column:end_time;type:datetime;comment:缁撴潫鏃堕棿"`
 		Content        string       `json:"content" gorm:"column:content;type:varchar(255);comment:璁″垝鍐呭"`
 		File           string       `json:"file" gorm:"column:file;type:varchar(255);comment:闄勪欢"`
 		gorm.Model     `json:"-"`
@@ -111,4 +110,4 @@
 func (slf *PlanSearch) SetOrder(order string) *PlanSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/quotation.go b/model/quotation.go
index 2999797..b97befb 100644
--- a/model/quotation.go
+++ b/model/quotation.go
@@ -3,25 +3,25 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	// Quotation 鎶ヤ环鍗�
 	Quotation struct {
-		Id                int        `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		ClientId          int        `json:"client_id" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
-		Number            string     `json:"number" gorm:"column:number;type:varchar(255);comment:鎶ヤ环鍗曞彿"`
-		QuotationStatusId int        `json:"quotation_status_id" gorm:"column:quotation_status_id;type:int;comment:鎶ヤ环鍗曠姸鎬乮d"`
-		ValidityDate      time.Time  `json:"validity_date" gorm:"column:validity_date;type:datetime;comment:鏈夋晥鏈�"`
-		ContactId         int        `json:"contact_id" gorm:"column:contact_id;type:int;comment:鑱旂郴浜篿d"`
-		MemberId          int        `json:"member_id" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
-		SaleChanceId      int        `json:"sale_chance_id" gorm:"column:sale_chance_id;type:int;comment:鍟嗘満id"`
-		Conditions        string     `json:"conditions" gorm:"column:conditions;type:text;comment:鎶ヤ环鏉′欢"`
-		File              string     `json:"file" gorm:"column:file;type:varchar(255);comment:闄勪欢"`
-		Client            Client     `json:"client" gorm:"foreignKey:ClientId"`
-		Contact           Contact    `json:"contact" gorm:"foreignKey:ContactId"`
-		SaleChance        SaleChance `json:"sale_chance" gorm:"foreignKey:SaleChanceId"`
+		Id                int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		ClientId          int         `json:"client_id" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
+		Number            string      `json:"number" gorm:"column:number;type:varchar(255);comment:鎶ヤ环鍗曞彿"`
+		QuotationStatusId int         `json:"quotation_status_id" gorm:"column:quotation_status_id;type:int;comment:鎶ヤ环鍗曠姸鎬乮d"`
+		ValidityDate      *CustomTime `json:"validity_date" gorm:"column:validity_date;type:datetime;comment:鏈夋晥鏈�"`
+		ContactId         int         `json:"contact_id" gorm:"column:contact_id;type:int;comment:鑱旂郴浜篿d"`
+		MemberId          int         `json:"member_id" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
+		SaleChanceId      int         `json:"sale_chance_id" gorm:"column:sale_chance_id;type:int;comment:閿�鍞満浼歩d"`
+		Conditions        string      `json:"conditions" gorm:"column:conditions;type:text;comment:鎶ヤ环鏉′欢"`
+		File              string      `json:"file" gorm:"column:file;type:varchar(255);comment:闄勪欢"`
+		Client            Client      `json:"client" gorm:"foreignKey:ClientId"`
+		Contact           Contact     `json:"contact" gorm:"foreignKey:ContactId"`
+		SaleChance        SaleChance  `json:"sale_chance" gorm:"foreignKey:SaleChanceId"`
+		Products          []Product   `json:"products" gorm:"many2many:quotation_product"`
 		gorm.Model        `json:"-"`
 	}
 
@@ -29,12 +29,11 @@
 	QuotationSearch struct {
 		Quotation
 
-				Orm      *gorm.DB
+		Orm      *gorm.DB
 		Keyword  string
 		OrderBy  string
 		PageNum  int
 		PageSize int
-
 	}
 )
 
@@ -120,4 +119,4 @@
 func (slf *QuotationSearch) SetOrder(order string) *QuotationSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/request/collectionProjection.go b/model/request/collectionProjection.go
new file mode 100644
index 0000000..579f593
--- /dev/null
+++ b/model/request/collectionProjection.go
@@ -0,0 +1,21 @@
+package request
+
+type AddCollectionProjection struct {
+	CollectionProjection
+	SaleChanceId int `json:"sale_chance_id"` // 閿�鍞満浼歩d
+}
+
+type CollectionProjection struct {
+	EstimatedCollectionAmount float64 `json:"estimated_collection_amount"` // 棰勮鏀舵閲戦
+	EstimatedCollectionTime   string  `json:"estimated_collection_time"`   // 棰勮鏀舵鏃堕棿
+}
+
+type UpdateCollectionProjection struct {
+	Id int `json:"id"`
+	CollectionProjection
+}
+
+type GetCollectionProjectionList struct {
+	PageInfo
+	SearchMap map[string]interface{}
+}
diff --git a/model/request/quotation.go b/model/request/quotation.go
index 8ee6098..f996a4c 100644
--- a/model/request/quotation.go
+++ b/model/request/quotation.go
@@ -1,19 +1,22 @@
 package request
 
+import "aps_crm/model"
+
 type AddQuotation struct {
 	Quotation
 }
 
 type Quotation struct {
-	ClientId          int    `json:"client_id"`
-	Number            string `json:"number"`
-	QuotationStatusId int    `json:"quotation_status_id"`
-	ValidityDate      string `json:"validity_date"`
-	ContactId         int    `json:"contact_id"`
-	MemberId          int    `json:"member_id"`
-	SaleChanceId      int    `json:"sale_chance_id"`
-	Conditions        string `json:"conditions"`
-	File              string `json:"file"`
+	ClientId          int              `json:"client_id"`
+	Number            string           `json:"number"`
+	QuotationStatusId int              `json:"quotation_status_id"`
+	ValidityDate      string           `json:"validity_date"`
+	ContactId         int              `json:"contact_id"`
+	MemberId          int              `json:"member_id"`
+	SaleChanceId      int              `json:"sale_chance_id"`
+	Conditions        string           `json:"conditions"`
+	File              string           `json:"file"`
+	Products          []*model.Product `json:"products"`
 }
 
 type UpdateQuotation struct {
diff --git a/model/request/saleChance.go b/model/request/saleChance.go
index 7d5c4f1..b175a1e 100644
--- a/model/request/saleChance.go
+++ b/model/request/saleChance.go
@@ -46,3 +46,8 @@
 	PageInfo
 	Keyword string `json:"keyword"`
 }
+
+type PushSaleChance struct {
+	Id   int `json:"id" binding:"required"`
+	Step int `json:"step" binding:"required"`
+}
diff --git a/model/response/response.go b/model/response/response.go
index 46774ab..cc15773 100644
--- a/model/response/response.go
+++ b/model/response/response.go
@@ -344,4 +344,9 @@
 	CurrencyResponse struct {
 		List []*model.Currency `json:"list"`
 	}
+
+	CollectionProjectionListResponse struct {
+		List  []*model.CollectionProjection `json:"list"`
+		Count int                           `json:"count"`
+	}
 )
diff --git a/model/saleChance.go b/model/saleChance.go
index 320cafb..3667873 100644
--- a/model/saleChance.go
+++ b/model/saleChance.go
@@ -3,44 +3,44 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	SaleChance struct {
-		Id                 int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		Name               string    `json:"name" gorm:"column:name;type:varchar(255);comment:鍏徃鍚嶇О"`
-		Number             string    `json:"number" gorm:"column:number;type:varchar(255);comment:閿�鍞嚎绱㈢紪鍙�"`
-		ContactId          int       `json:"contact_id" gorm:"column:contact_id;type:int(11);comment:鑱旂郴浜篒D"`
-		ClientId           int       `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
-		SalesSourcesId     int       `json:"sales_sources_id" gorm:"column:sales_sources_id;type:int(11);comment:鍟嗘満鏉ユ簮ID"`
-		SaleTypeId         int       `json:"sale_type_id" gorm:"column:sale_type_id;type:int(11);comment:鍟嗘満绫诲瀷ID"`
-		SaleStageId        int       `json:"sale_stage_id" gorm:"column:sale_stage_id;type:int(11);comment:鍟嗘満闃舵ID"`
-		MemberId           int       `json:"member_id" gorm:"column:member_id;type:int(11);comment:閿�鍞礋璐d汉ID"`
-		RegularCustomersId int       `json:"regular_customers_id" gorm:"column:regular_customers_id;type:int(11);comment:甯稿ID"`
-		Competitors        string    `json:"competitors" gorm:"column:competitors;type:varchar(255);comment:绔炰簤瀵规墜"`
-		PossibilitiesId    int       `json:"possibilities_id" gorm:"column:possibilities_id;type:int(11);comment:鍙兘鎬D"`
-		Budget             float64   `json:"budget" gorm:"column:budget;type:decimal(10,2);comment:棰勭畻"`
-		ProjectedAmount    float64   `json:"projected_amount" gorm:"column:projected_amount;type:decimal(10,2);comment:棰勮閲戦"`
-		Currency           int       `json:"currency" gorm:"column:currency;type:int(11);comment:甯佺"`
-		ExpectedTime       time.Time `json:"expected_time" gorm:"column:expected_time;type:datetime;comment:棰勮鎴愪氦鏃堕棿"`
-		StatusId           int       `json:"status_id" gorm:"column:status_id;type:int(11);comment:鐘舵�両D"`
-		PainPoints         string    `json:"pain_points" gorm:"column:pain_points;type:text;comment:鐥涚偣"`
-		WhetherEstablished string    `json:"whether_established" gorm:"column:whether_established;type:text;comment:鏄惁鎴愮珛"`
-		CapitalBudget      string    `json:"capital_budget" gorm:"column:capital_budget;type:text;comment:璧勯噾棰勭畻"`
-		KeyMaker           string    `json:"key_maker" gorm:"column:key_maker;type:text;comment:鍏抽敭浜�"`
-		KeyFactors         string    `json:"key_factors" gorm:"column:key_factors;type:text;comment:鍏抽敭鍥犵礌"`
-		Process            string    `json:"process" gorm:"column:process;type:text;comment:鍐崇瓥娴佺▼"`
-		Solutions          string    `json:"solutions" gorm:"column:solutions;type:text;comment:绔炰簤瀵规墜瑙e喅鏂规"`
-		Advantages         string    `json:"advantages" gorm:"column:advantages;type:text;comment:绔炰簤浼樺娍"`
-		Disadvantages      string    `json:"disadvantages" gorm:"column:disadvantages;type:text;comment:绔炰簤鍔e娍"`
-		Opportunities      string    `json:"opportunities" gorm:"column:opportunities;type:text;comment:绔炰簤鏈轰細"`
-		Threats            string    `json:"threats" gorm:"column:threats;type:text;comment:绔炰簤濞佽儊"`
-		DetailAddress      string    `json:"detail_address" gorm:"column:detail_address;type:text;comment:璇︾粏鍦板潃"`
-		Remark             string    `json:"remark" gorm:"column:remark;type:text;comment:澶囨敞"`
-		Contact            Contact   `json:"contact" gorm:"foreignKey:ContactId;references:Id"`
-		Client             Client    `json:"client" gorm:"foreignKey:ClientId;references:Id"`
-		SalesSources       SalesSources
+		Id                    int                    `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		Name                  string                 `json:"name" gorm:"column:name;type:varchar(255);comment:鍏徃鍚嶇О"`
+		Number                string                 `json:"number" gorm:"column:number;type:varchar(255);comment:閿�鍞嚎绱㈢紪鍙�"`
+		ContactId             int                    `json:"contact_id" gorm:"column:contact_id;type:int(11);comment:鑱旂郴浜篒D"`
+		ClientId              int                    `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
+		SalesSourcesId        int                    `json:"sales_sources_id" gorm:"column:sales_sources_id;type:int(11);comment:鍟嗘満鏉ユ簮ID"`
+		SaleTypeId            int                    `json:"sale_type_id" gorm:"column:sale_type_id;type:int(11);comment:鍟嗘満绫诲瀷ID"`
+		SaleStageId           int                    `json:"sale_stage_id" gorm:"column:sale_stage_id;type:int(11);comment:鍟嗘満闃舵ID"`
+		MemberId              int                    `json:"member_id" gorm:"column:member_id;type:int(11);comment:閿�鍞礋璐d汉ID"`
+		RegularCustomersId    int                    `json:"regular_customers_id" gorm:"column:regular_customers_id;type:int(11);comment:甯稿ID"`
+		Competitors           string                 `json:"competitors" gorm:"column:competitors;type:varchar(255);comment:绔炰簤瀵规墜"`
+		PossibilitiesId       int                    `json:"possibilities_id" gorm:"column:possibilities_id;type:int(11);comment:鍙兘鎬D"`
+		Budget                float64                `json:"budget" gorm:"column:budget;type:decimal(10,2);comment:棰勭畻"`
+		ProjectedAmount       float64                `json:"projected_amount" gorm:"column:projected_amount;type:decimal(10,2);comment:棰勮閲戦"`
+		Currency              int                    `json:"currency" gorm:"column:currency;type:int(11);comment:甯佺"`
+		ExpectedTime          *CustomTime            `json:"expected_time" gorm:"column:expected_time;type:datetime;comment:棰勮鎴愪氦鏃堕棿"`
+		StatusId              int                    `json:"status_id" gorm:"column:status_id;type:int(11);comment:鐘舵�両D"`
+		PainPoints            string                 `json:"pain_points" gorm:"column:pain_points;type:text;comment:鐥涚偣"`
+		WhetherEstablished    string                 `json:"whether_established" gorm:"column:whether_established;type:text;comment:鏄惁鎴愮珛"`
+		CapitalBudget         string                 `json:"capital_budget" gorm:"column:capital_budget;type:text;comment:璧勯噾棰勭畻"`
+		KeyMaker              string                 `json:"key_maker" gorm:"column:key_maker;type:text;comment:鍏抽敭浜�"`
+		KeyFactors            string                 `json:"key_factors" gorm:"column:key_factors;type:text;comment:鍏抽敭鍥犵礌"`
+		Process               string                 `json:"process" gorm:"column:process;type:text;comment:鍐崇瓥娴佺▼"`
+		Solutions             string                 `json:"solutions" gorm:"column:solutions;type:text;comment:绔炰簤瀵规墜瑙e喅鏂规"`
+		Advantages            string                 `json:"advantages" gorm:"column:advantages;type:text;comment:绔炰簤浼樺娍"`
+		Disadvantages         string                 `json:"disadvantages" gorm:"column:disadvantages;type:text;comment:绔炰簤鍔e娍"`
+		Opportunities         string                 `json:"opportunities" gorm:"column:opportunities;type:text;comment:绔炰簤鏈轰細"`
+		Threats               string                 `json:"threats" gorm:"column:threats;type:text;comment:绔炰簤濞佽儊"`
+		DetailAddress         string                 `json:"detail_address" gorm:"column:detail_address;type:text;comment:璇︾粏鍦板潃"`
+		Remark                string                 `json:"remark" gorm:"column:remark;type:text;comment:澶囨敞"`
+		Contact               Contact                `json:"contact" gorm:"foreignKey:ContactId;references:Id"`
+		Client                Client                 `json:"client" gorm:"foreignKey:ClientId;references:Id"`
+		CollectionProjections []CollectionProjection `json:"collection_projections" gorm:"foreignKey:SaleChanceId"`
+		SalesSources          SalesSources
 		Address
 		gorm.Model `json:"-"`
 	}
@@ -114,7 +114,7 @@
 		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
 	}
 
-	err := db.Preload("Client").Preload("Contact").Find(&records).Error
+	err := db.Preload("CollectionProjections").Preload("Client").Preload("Contact").Find(&records).Error
 	return records, total, err
 }
 
@@ -141,4 +141,4 @@
 func (slf *SaleChanceSearch) SetOrder(order string) *SaleChanceSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/salesRefund.go b/model/salesRefund.go
index e34be5b..cbdf41b 100644
--- a/model/salesRefund.go
+++ b/model/salesRefund.go
@@ -3,33 +3,31 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	SalesRefund struct {
-		Id           int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		ClientId     int       `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
-		Number       string    `json:"number" gorm:"column:number;type:varchar(255);comment:閫�娆惧崟鍙�"`
-		MemberId     int       `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
-		RefundDate   time.Time `json:"refundDate" gorm:"column:refund_date;type:datetime;comment:閫�娆炬棩鏈�"`
-		RefundMethod string    `json:"refundMethod" gorm:"column:refund_method;type:varchar(255);comment:閫�娆炬柟寮�"`
-		AccountId    int       `json:"accountId" gorm:"column:account_id;type:int;comment:璐︽埛"`
-		IsInvoice    int       `json:"isInvoice" gorm:"column:is_invoice;type:int;comment:鏄惁寮�绁�"`
-		Reason       string    `json:"reason" gorm:"column:reason;type:varchar(255);comment:閫�娆惧師鍥�"`
-		Products     []Product `json:"products" gorm:"many2many:salesRefund_product;"`
+		Id           int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		ClientId     int         `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
+		Number       string      `json:"number" gorm:"column:number;type:varchar(255);comment:閫�娆惧崟鍙�"`
+		MemberId     int         `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
+		RefundDate   *CustomTime `json:"refundDate" gorm:"column:refund_date;type:datetime;comment:閫�娆炬棩鏈�"`
+		RefundMethod string      `json:"refundMethod" gorm:"column:refund_method;type:varchar(255);comment:閫�娆炬柟寮�"`
+		AccountId    int         `json:"accountId" gorm:"column:account_id;type:int;comment:璐︽埛"`
+		IsInvoice    int         `json:"isInvoice" gorm:"column:is_invoice;type:int;comment:鏄惁寮�绁�"`
+		Reason       string      `json:"reason" gorm:"column:reason;type:varchar(255);comment:閫�娆惧師鍥�"`
+		Products     []Product   `json:"products" gorm:"many2many:salesRefund_product;"`
 		gorm.Model   `json:"-"`
 	}
 
 	SalesRefundSearch struct {
 		SalesRefund
 
-				Orm      *gorm.DB
+		Orm      *gorm.DB
 		Keyword  string
 		OrderBy  string
 		PageNum  int
 		PageSize int
-
 	}
 )
 
@@ -114,4 +112,4 @@
 func (slf *SalesRefundSearch) SetOrder(order string) *SalesRefundSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/salesReturn.go b/model/salesReturn.go
index 94e1232..646ca70 100644
--- a/model/salesReturn.go
+++ b/model/salesReturn.go
@@ -3,20 +3,19 @@
 import (
 	"aps_crm/pkg/mysqlx"
 	"gorm.io/gorm"
-	"time"
 )
 
 type (
 	SalesReturn struct {
-		Id                int       `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
-		ClientId          int       `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
-		Number            string    `json:"number" gorm:"column:number;type:varchar(255);comment:閫�璐у崟鍙�"`
-		Repository        string    `json:"repository" gorm:"column:repository;type:varchar(255);comment:浠撳簱"`
-		MemberId          int       `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
-		ReturnDate        time.Time `json:"returnDate" gorm:"column:return_date;type:datetime;comment:閫�璐ф棩鏈�"`
-		SalesReturnStatus int       `json:"salesReturnStatus" gorm:"column:sales_return_status;type:int;comment:閫�璐х姸鎬�"`
-		Reason            string    `json:"reason" gorm:"column:reason;type:varchar(255);comment:閫�璐у師鍥�"`
-		Products          []Product `json:"products" gorm:"many2many:salesReturn_product;"`
+		Id                int         `json:"id" gorm:"column:id;primary_key;AUTO_INCREMENT"`
+		ClientId          int         `json:"clientId" gorm:"column:client_id;type:int;comment:瀹㈡埛id"`
+		Number            string      `json:"number" gorm:"column:number;type:varchar(255);comment:閫�璐у崟鍙�"`
+		Repository        string      `json:"repository" gorm:"column:repository;type:varchar(255);comment:浠撳簱"`
+		MemberId          int         `json:"memberId" gorm:"column:member_id;type:int;comment:璐熻矗浜篿d"`
+		ReturnDate        *CustomTime `json:"returnDate" gorm:"column:return_date;type:datetime;comment:閫�璐ф棩鏈�"`
+		SalesReturnStatus int         `json:"salesReturnStatus" gorm:"column:sales_return_status;type:int;comment:閫�璐х姸鎬�"`
+		Reason            string      `json:"reason" gorm:"column:reason;type:varchar(255);comment:閫�璐у師鍥�"`
+		Products          []Product   `json:"products" gorm:"many2many:salesReturn_product;"`
 	}
 
 	SalesReturnSearch struct {
@@ -107,4 +106,4 @@
 func (slf *SalesReturnSearch) SetOrder(order string) *SalesReturnSearch {
 	slf.OrderBy = order
 	return slf
-}
\ No newline at end of file
+}
diff --git a/model/serviceFeeManage.go b/model/serviceFeeManage.go
index 16ec527..39f7e9a 100644
--- a/model/serviceFeeManage.go
+++ b/model/serviceFeeManage.go
@@ -1,176 +1,176 @@
-package model
-
-import (
-	"aps_crm/constvar"
-	"aps_crm/pkg/mysqlx"
-	"gorm.io/gorm"
-	"time"
-)
-
-type (
-	ServiceFeeManage struct {
-		Id         int       `json:"id" gorm:"column:id;primaryKey;autoIncrement;not null"`
-		ClientId   int       `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
-		Client     *Client   `json:"client" gorm:"foreignKey:ClientId"`
-		MemberId   int       `json:"member_id" gorm:"column:member_id;type:int(11);comment:鍛樺伐ID"`
-		LatestDate time.Time `json:"latest_date" gorm:"column:latest_date;type:datetime;comment:鏈�鏅氭湇鍔℃椂闂�"`
-		Remark     string    `json:"remark" gorm:"column:remark;type:varchar(255);comment:澶囨敞"`
-		File       string    `json:"file" gorm:"column:file;type:varchar(255);comment:鏂囦欢"`
-		gorm.Model `json:"-"`
-	}
-
-	ServiceFeeManageSearch struct {
-		ServiceFeeManage
-		Orm         *gorm.DB
-		QueryClass  constvar.ServiceFeeQueryClass
-		KeywordType constvar.ServiceFeeKeywordType
-		Keyword     string
-		OrderBy     string
-		PageNum     int
-		PageSize    int
-	}
-)
-
-func (ServiceFeeManage) TableName() string {
-	return "service_fee_manage"
-}
-
-func NewServiceFeeManageSearch(db *gorm.DB) *ServiceFeeManageSearch {
-	if db == nil {
-		db = mysqlx.GetDB()
-	}
-
-	return &ServiceFeeManageSearch{
-		Orm: db,
-	}
-}
-
-func (slf *ServiceFeeManageSearch) build() *gorm.DB {
-	var db = slf.Orm.Model(&ServiceFeeManage{})
-	if slf.Id != 0 {
-		db.Where("id = ?", slf.Id)
-	}
-	if slf.ClientId != 0 {
-		db.Where("client_id = ?", slf.ClientId)
-	}
-	switch slf.QueryClass {
-	case constvar.ServiceFeeQueryClassExpireLessThen60Days:
-		db = db.Where("latest_date > ? and latest_date < ?", time.Now(), time.Now().AddDate(0, 0, 60))
-	case constvar.ServiceFeeQueryClassExpireLessThen30Days:
-		db = db.Where("latest_date > ? and latest_date < ?", time.Now(), time.Now().AddDate(0, 0, 30))
-	case constvar.ServiceFeeQueryClassExpireAboutTo60Day:
-		db = db.Where("latest_date = ?", time.Now().AddDate(0, 0, -60))
-	case constvar.ServiceFeeQueryClassExpireAboutTo30Day:
-		db = db.Where("latest_date = ?", time.Now().AddDate(0, 0, -30))
-	case constvar.ServiceFeeQueryClassExpired:
-		db = db.Where("latest_date < ?", time.Now())
-	case constvar.ServiceFeeQueryClassNoService:
-		db = db.Where("latest_date < ?", time.Now().AddDate(0, 0, -60))
-
-	}
-
-	switch slf.KeywordType {
-	case constvar.ServiceFeeKeywordCustomerName:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.name = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordCustomerType:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.client_type_id = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordSalesPrincipal:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.member_id = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordCustomerScale:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.enterprise_scale_id = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordClientLevel:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.client_level_id = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordCustomerNo:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.number = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordCustomerStatus:
-		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
-		db = db.Where("clients.client_status_id = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordServiceEndDate:
-		db = db.Where("latest_date = ?", slf.Keyword)
-	case constvar.ServiceFeeKeywordProductName:
-		//todo
-	}
-
-	return db
-}
-
-func (slf *ServiceFeeManageSearch) Create(record *ServiceFeeManage) error {
-	var db = slf.build()
-	return db.Create(record).Error
-}
-
-func (slf *ServiceFeeManageSearch) Update(record *ServiceFeeManage) error {
-	var db = slf.build()
-	return db.Updates(record).Error
-}
-
-func (slf *ServiceFeeManageSearch) Delete() error {
-	var db = slf.build()
-	return db.Delete(&ServiceFeeManage{}).Error
-}
-
-func (slf *ServiceFeeManageSearch) SetId(id int) *ServiceFeeManageSearch {
-	slf.Id = id
-	return slf
-}
-
-func (slf *ServiceFeeManageSearch) SetKeywordType(keyword constvar.ServiceFeeKeywordType) *ServiceFeeManageSearch {
-	slf.KeywordType = keyword
-	return slf
-}
-
-func (slf *ServiceFeeManageSearch) SetQueryClass(queryClass constvar.ServiceFeeQueryClass) *ServiceFeeManageSearch {
-	slf.QueryClass = queryClass
-	return slf
-}
-
-func (slf *ServiceFeeManageSearch) SetKeyword(keyword string) *ServiceFeeManageSearch {
-	slf.Keyword = keyword
-	return slf
-}
-
-func (slf *ServiceFeeManageSearch) Find() (*ServiceFeeManage, error) {
-	var db = slf.build()
-	var record = new(ServiceFeeManage)
-	err := db.First(record).Error
-	return record, err
-}
-
-func (slf *ServiceFeeManageSearch) FindAll() ([]*ServiceFeeManage, int64, error) {
-	var db = slf.build()
-	var records = make([]*ServiceFeeManage, 0)
-	var total int64
-	if err := db.Count(&total).Error; err != nil {
-		return records, total, err
-	}
-	if slf.PageNum > 0 && slf.PageSize > 0 {
-		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
-	}
-
-	if slf.PageNum > 0 && slf.PageSize > 0 {
-		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
-	}
-
-	err := db.Preload("Client").Find(&records).Error
-	return records, total, err
-}
-
-func (slf *ServiceFeeManageSearch) SetPage(page, size int) *ServiceFeeManageSearch {
-	slf.PageNum, slf.PageSize = page, size
-	return slf
-}
-
-func (slf *ServiceFeeManageSearch) SetOrder(order string) *ServiceFeeManageSearch {
-	slf.OrderBy = order
-	return slf
-}
-func (slf *ServiceFeeManageSearch) SetIds(ids []int) *ServiceFeeManageSearch {
-	slf.Orm = slf.Orm.Where("id in (?)", ids)
-	return slf
-}
+package model
+
+import (
+	"aps_crm/constvar"
+	"aps_crm/pkg/mysqlx"
+	"gorm.io/gorm"
+	"time"
+)
+
+type (
+	ServiceFeeManage struct {
+		Id         int         `json:"id" gorm:"column:id;primaryKey;autoIncrement;not null"`
+		ClientId   int         `json:"client_id" gorm:"column:client_id;type:int(11);comment:瀹㈡埛ID"`
+		Client     *Client     `json:"client" gorm:"foreignKey:ClientId"`
+		MemberId   int         `json:"member_id" gorm:"column:member_id;type:int(11);comment:鍛樺伐ID"`
+		LatestDate *CustomTime `json:"latest_date" gorm:"column:latest_date;type:datetime;comment:鏈�鏅氭湇鍔℃椂闂�"`
+		Remark     string      `json:"remark" gorm:"column:remark;type:varchar(255);comment:澶囨敞"`
+		File       string      `json:"file" gorm:"column:file;type:varchar(255);comment:鏂囦欢"`
+		gorm.Model `json:"-"`
+	}
+
+	ServiceFeeManageSearch struct {
+		ServiceFeeManage
+		Orm         *gorm.DB
+		QueryClass  constvar.ServiceFeeQueryClass
+		KeywordType constvar.ServiceFeeKeywordType
+		Keyword     string
+		OrderBy     string
+		PageNum     int
+		PageSize    int
+	}
+)
+
+func (ServiceFeeManage) TableName() string {
+	return "service_fee_manage"
+}
+
+func NewServiceFeeManageSearch(db *gorm.DB) *ServiceFeeManageSearch {
+	if db == nil {
+		db = mysqlx.GetDB()
+	}
+
+	return &ServiceFeeManageSearch{
+		Orm: db,
+	}
+}
+
+func (slf *ServiceFeeManageSearch) build() *gorm.DB {
+	var db = slf.Orm.Model(&ServiceFeeManage{})
+	if slf.Id != 0 {
+		db.Where("id = ?", slf.Id)
+	}
+	if slf.ClientId != 0 {
+		db.Where("client_id = ?", slf.ClientId)
+	}
+	switch slf.QueryClass {
+	case constvar.ServiceFeeQueryClassExpireLessThen60Days:
+		db = db.Where("latest_date > ? and latest_date < ?", time.Now(), time.Now().AddDate(0, 0, 60))
+	case constvar.ServiceFeeQueryClassExpireLessThen30Days:
+		db = db.Where("latest_date > ? and latest_date < ?", time.Now(), time.Now().AddDate(0, 0, 30))
+	case constvar.ServiceFeeQueryClassExpireAboutTo60Day:
+		db = db.Where("latest_date = ?", time.Now().AddDate(0, 0, -60))
+	case constvar.ServiceFeeQueryClassExpireAboutTo30Day:
+		db = db.Where("latest_date = ?", time.Now().AddDate(0, 0, -30))
+	case constvar.ServiceFeeQueryClassExpired:
+		db = db.Where("latest_date < ?", time.Now())
+	case constvar.ServiceFeeQueryClassNoService:
+		db = db.Where("latest_date < ?", time.Now().AddDate(0, 0, -60))
+
+	}
+
+	switch slf.KeywordType {
+	case constvar.ServiceFeeKeywordCustomerName:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.name = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordCustomerType:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.client_type_id = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordSalesPrincipal:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.member_id = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordCustomerScale:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.enterprise_scale_id = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordClientLevel:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.client_level_id = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordCustomerNo:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.number = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordCustomerStatus:
+		db.Joins("left join clients on clients.id = service_fee_manage.client_id")
+		db = db.Where("clients.client_status_id = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordServiceEndDate:
+		db = db.Where("latest_date = ?", slf.Keyword)
+	case constvar.ServiceFeeKeywordProductName:
+		//todo
+	}
+
+	return db
+}
+
+func (slf *ServiceFeeManageSearch) Create(record *ServiceFeeManage) error {
+	var db = slf.build()
+	return db.Create(record).Error
+}
+
+func (slf *ServiceFeeManageSearch) Update(record *ServiceFeeManage) error {
+	var db = slf.build()
+	return db.Updates(record).Error
+}
+
+func (slf *ServiceFeeManageSearch) Delete() error {
+	var db = slf.build()
+	return db.Delete(&ServiceFeeManage{}).Error
+}
+
+func (slf *ServiceFeeManageSearch) SetId(id int) *ServiceFeeManageSearch {
+	slf.Id = id
+	return slf
+}
+
+func (slf *ServiceFeeManageSearch) SetKeywordType(keyword constvar.ServiceFeeKeywordType) *ServiceFeeManageSearch {
+	slf.KeywordType = keyword
+	return slf
+}
+
+func (slf *ServiceFeeManageSearch) SetQueryClass(queryClass constvar.ServiceFeeQueryClass) *ServiceFeeManageSearch {
+	slf.QueryClass = queryClass
+	return slf
+}
+
+func (slf *ServiceFeeManageSearch) SetKeyword(keyword string) *ServiceFeeManageSearch {
+	slf.Keyword = keyword
+	return slf
+}
+
+func (slf *ServiceFeeManageSearch) Find() (*ServiceFeeManage, error) {
+	var db = slf.build()
+	var record = new(ServiceFeeManage)
+	err := db.First(record).Error
+	return record, err
+}
+
+func (slf *ServiceFeeManageSearch) FindAll() ([]*ServiceFeeManage, int64, error) {
+	var db = slf.build()
+	var records = make([]*ServiceFeeManage, 0)
+	var total int64
+	if err := db.Count(&total).Error; err != nil {
+		return records, total, err
+	}
+	if slf.PageNum > 0 && slf.PageSize > 0 {
+		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
+	}
+
+	if slf.PageNum > 0 && slf.PageSize > 0 {
+		db = db.Limit(slf.PageSize).Offset((slf.PageNum - 1) * slf.PageSize)
+	}
+
+	err := db.Preload("Client").Find(&records).Error
+	return records, total, err
+}
+
+func (slf *ServiceFeeManageSearch) SetPage(page, size int) *ServiceFeeManageSearch {
+	slf.PageNum, slf.PageSize = page, size
+	return slf
+}
+
+func (slf *ServiceFeeManageSearch) SetOrder(order string) *ServiceFeeManageSearch {
+	slf.OrderBy = order
+	return slf
+}
+func (slf *ServiceFeeManageSearch) SetIds(ids []int) *ServiceFeeManageSearch {
+	slf.Orm = slf.Orm.Where("id in (?)", ids)
+	return slf
+}
diff --git a/pkg/ecode/code.go b/pkg/ecode/code.go
index 6a3d1a2..9701a5a 100644
--- a/pkg/ecode/code.go
+++ b/pkg/ecode/code.go
@@ -393,4 +393,9 @@
 	AssignWrongId        = 5700003 // 鍒嗛厤澶辫触锛屽垎閰嶅璞′负绌�
 	AssignWrongModelType = 5700004 // 鍒嗛厤澶辫触锛屽垎閰嶅璞$被鍨嬩负绌�
 
+	CollectionProjectionExist     = 5800001 // 鏀舵棰勬祴宸插瓨鍦�
+	CollectionProjectionNotExist  = 5800002 // 鏀舵棰勬祴涓嶅瓨鍦�
+	CollectionProjectionListErr   = 5800003 // 鑾峰彇鏀舵棰勬祴鍒楄〃澶辫触
+	CollectionProjectionSetErr    = 5800004 // 璁剧疆鏀舵棰勬祴澶辫触
+	CollectionProjectionUpdateErr = 5800005 // 鏇存柊鏀舵棰勬祴澶辫触
 )
diff --git a/router/colletionProjection.go b/router/colletionProjection.go
new file mode 100644
index 0000000..7a6f098
--- /dev/null
+++ b/router/colletionProjection.go
@@ -0,0 +1,19 @@
+package router
+
+import (
+	v1 "aps_crm/api/v1"
+	"github.com/gin-gonic/gin"
+)
+
+type CollectionProjectionRouter struct{}
+
+func (c *CollectionProjectionRouter) InitCollectionProjectionRouter(router *gin.RouterGroup) {
+	collectionProjectionRouter := router.Group("collectionProjection")
+	collectionProjectionApi := v1.ApiGroup.CollectionProjectionApi
+	{
+		collectionProjectionRouter.POST("add", collectionProjectionApi.Add)             // 娣诲姞鏀舵棰勬祴
+		collectionProjectionRouter.DELETE("delete/:id", collectionProjectionApi.Delete) // 鍒犻櫎鏀舵棰勬祴
+		collectionProjectionRouter.PUT("update", collectionProjectionApi.Update)        // 鏇存柊鏀舵棰勬祴
+		collectionProjectionRouter.POST("list", collectionProjectionApi.List)           // 鑾峰彇鏀舵棰勬祴鍒楄〃
+	}
+}
diff --git a/router/index.go b/router/index.go
index 1b0ecd3..8f21017 100644
--- a/router/index.go
+++ b/router/index.go
@@ -70,6 +70,7 @@
 	DepartmentRouter
 	SatisfactionRouter
 	AssignRouter
+	CollectionProjectionRouter
 }
 
 func InitRouter() *gin.Engine {
@@ -176,7 +177,7 @@
 		InitInvoiceTypeRouter(PrivateGroup)
 		InitCourierCompanyRouter(PrivateGroup)
 		InitProductRouter(PrivateGroup)
-
+		routerGroup.InitCollectionProjectionRouter(PrivateGroup)
 	}
 	return Router
 }
diff --git a/router/saleChance.go b/router/saleChance.go
index 5844d07..93cc68a 100644
--- a/router/saleChance.go
+++ b/router/saleChance.go
@@ -14,6 +14,7 @@
 		saleChanceRouter.POST("add", saleChanceApi.Add)             // 娣诲姞閿�鍞満浼�
 		saleChanceRouter.DELETE("delete/:id", saleChanceApi.Delete) // 鍒犻櫎閿�鍞満浼�
 		saleChanceRouter.PUT("update", saleChanceApi.Update)        // 鏇存柊閿�鍞満浼�
-		saleChanceRouter.POST("list", saleChanceApi.List)            // 鑾峰彇閿�鍞満浼氬垪琛�
+		saleChanceRouter.POST("list", saleChanceApi.List)           // 鑾峰彇閿�鍞満浼氬垪琛�
+		saleChanceRouter.PUT("push", saleChanceApi.Push)            // 鎺ㄨ繘閿�鍞満浼�
 	}
-}
\ No newline at end of file
+}
diff --git a/service/collectionProjection.go b/service/collectionProjection.go
new file mode 100644
index 0000000..e482aa4
--- /dev/null
+++ b/service/collectionProjection.go
@@ -0,0 +1,45 @@
+package service
+
+import (
+	"aps_crm/model"
+	"aps_crm/pkg/ecode"
+)
+
+type CollectionProjectionService struct{}
+
+func (CollectionProjectionService) AddCollectionProjection(collectionProjection *model.CollectionProjection) int {
+	err := model.NewCollectionProjectionSearch().Create(collectionProjection)
+	if err != nil {
+		return ecode.CollectionProjectionExist
+	}
+
+	return ecode.OK
+}
+
+func (CollectionProjectionService) UpdateCollectionProjection(collectionProjection *model.CollectionProjection) int {
+	err := model.NewCollectionProjectionSearch().SetID(collectionProjection.Id).Update(collectionProjection)
+	if err != nil {
+		return ecode.CollectionProjectionUpdateErr
+	}
+
+	return ecode.OK
+}
+
+func (CollectionProjectionService) DeleteCollectionProjection(id int) int {
+	err := model.NewCollectionProjectionSearch().SetID(id).Delete()
+	if err != nil {
+		return ecode.CollectionProjectionNotExist
+	}
+
+	return ecode.OK
+}
+
+func (CollectionProjectionService) GetCollectionProjectionList(page, pageSize int, data map[string]interface{}) (int64, int, []*model.CollectionProjection) {
+
+	total, err, list := model.NewCollectionProjectionSearch().SetPage(page, pageSize).SetSearchMap(data).Find()
+	if err != nil {
+		return total, ecode.CollectionProjectionListErr, list
+	}
+
+	return total, ecode.OK, list
+}
diff --git a/service/index.go b/service/index.go
index ae1c0c6..e76998e 100644
--- a/service/index.go
+++ b/service/index.go
@@ -61,6 +61,7 @@
 	QuotationStatusService
 	CurrencyService
 	AssignService
+	CollectionProjectionService
 }
 
 var ServiceGroup = new(Group)
diff --git a/service/saleChance.go b/service/saleChance.go
index 4c42d23..24dedae 100644
--- a/service/saleChance.go
+++ b/service/saleChance.go
@@ -62,3 +62,25 @@
 	}
 	return contacts, total, ecode.OK
 }
+
+// push
+func (SaleChanceService) PushSaleChance(id, step int) int {
+	// check saleChange exist
+	errCode := CheckSaleChangeExist(id)
+	if errCode != ecode.OK {
+		return errCode
+	}
+	// check step
+	_, err := model.NewSaleStageSearch().SetId(step).Find()
+	if err != nil {
+		return ecode.SaleStageNotExist
+	}
+
+	// push saleChange
+	err = model.NewSaleChanceSearch().SetId(id).Update(&model.SaleChance{SaleStageId: step})
+	if err != nil {
+		return ecode.SaleChanceUpdateErr
+	}
+
+	return ecode.OK
+}

--
Gitblit v1.8.0