浏览代码

test 开票/到款登记对应合并汇总、商品到款统计列表、编辑付款方式、分配套餐金额

hsun 2 年之前
父节点
当前提交
a72bd2f47f

+ 214 - 0
controller/census/invoice_payment.go

@@ -0,0 +1,214 @@
+package census
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/fms_api/controller/resp"
+	"hongze/fms_api/global"
+	"hongze/fms_api/models/base"
+	"hongze/fms_api/models/fms"
+	fmsService "hongze/fms_api/services/fms"
+	"hongze/fms_api/utils"
+	"strings"
+)
+
+// InvoicePaymentController 商品到款统计
+type InvoicePaymentController struct{}
+
+// List
+// @Title 商品到款统计列表
+// @Description 商品到款统计列表
+// @Param   Keyword			query	string	false	"关键词"
+// @Param   SellGroupId		query	int		false	"销售组别ID"
+// @Param   ServiceType		query	int		false	"套餐类型"
+// @Param   StartDate		query	string	false	"合同开始日期"
+// @Param   EndDate			query	string	false	"合同结束日期"
+// @Param   TimeType		query	int		false	"时间类型: 1-开票时间; 2-到款时间"
+// @Param   HasInvoice		query	int		false	"已开票"
+// @Param   HasPayment		query	int		false	"已到款"
+// @Success 200 {object} fms.ContractRegisterItem
+// @router /census/invoice_payment/list [get]
+func (ct *InvoicePaymentController) List(c *gin.Context) {
+	var req fms.InvoicePaymentCensusListReq
+	if e := c.BindQuery(&req); e != nil {
+		err, ok := e.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+e.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", err.Translate(global.Trans), c)
+		return
+	}
+
+	cond := `1 = 1`
+	pars := make([]interface{}, 0)
+	// 合同编号/客户姓名/销售/实际使用方
+	if req.Keyword != "" {
+		kw := "%" + req.Keyword + "%"
+		cond += ` b.company_name LIKE ? OR a.seller_name LIKE ?`
+		pars = append(pars, kw, kw)
+	}
+	if req.SellGroupId > 0 {
+		cond += ` AND a.seller_group_id = ?`
+		pars = append(pars, req.SellGroupId)
+	}
+	// 套餐筛选
+	if req.ServiceType != 0 {
+		registerIds, e := fms.GetContractRegisterIdsByTempId(req.ServiceType)
+		if e != nil {
+			resp.FailMsg("获取失败", "获取合同登记IDs失败, Err: "+e.Error(), c)
+			return
+		}
+		if len(registerIds) > 0 {
+			cond += ` AND contract_register_id IN ?`
+			pars = append(pars, registerIds)
+		} else {
+			cond += ` AND 1 = 2`
+		}
+	}
+	// 开票到款日期
+	if req.TimeType > 0 && req.StartDate != "" && req.EndDate != "" {
+		st := fmt.Sprint(req.StartDate, " 00:00:00")
+		ed := fmt.Sprint(req.EndDate, " 23:59:59")
+		cond += ` a.invoice_type = ? AND (invoice_time BETWEEN ? AND ?)`
+		pars = append(pars, req.TimeType, st, ed)
+	}
+	// 已开票
+	if req.HasInvoice == 1 && req.HasPayment == 0 {
+		cond += ` AND a.invoice_type = 1`
+	}
+	// 已到款
+	if req.HasInvoice == 0 && req.HasPayment == 1 {
+		cond += ` AND a.invoice_type = 2`
+	}
+
+	page := new(base.Page)
+	page.SetPageSize(req.PageSize)
+	page.SetCurrent(req.Current)
+	page.AddOrderItem(base.OrderItem{Column: "a.create_time", Asc: false})
+
+	total, list, e := fms.GetInvoicePaymentCensusPageList(page, cond, pars)
+	if e != nil {
+		resp.FailMsg("获取失败", "获取商品到款统计列表失败, Err: "+e.Error(), c)
+		return
+	}
+	registerIds := make([]int, 0)
+	for i := range list {
+		registerIds = append(registerIds, list[i].ContractRegisterId)
+	}
+
+	results := new(fms.InvoicePaymentCensusResp)
+	if len(registerIds) > 0 {
+		// 获取开票到款列表
+		ivCond := `contract_register_id IN ?`
+		ivPars := make([]interface{}, 0)
+		ivPars = append(ivPars, registerIds)
+		iv := new(fms.ContractInvoice)
+		invoiceList, e := iv.List(ivCond, ivPars, "")
+		if e != nil {
+			resp.FailMsg("获取失败", "获取开票到款列表失败, Err: "+e.Error(), c)
+			return
+		}
+		// 取出开票、到款
+		invoiceMap := make(map[int][]*fms.ContractInvoice, 0)
+		paymentMap := make(map[int][]*fms.ContractInvoice, 0)
+		paymentIds := make([]int, 0)
+		for i := range invoiceList {
+			if invoiceMap[invoiceList[i].ContractRegisterId] == nil {
+				invoiceMap[invoiceList[i].ContractRegisterId] = make([]*fms.ContractInvoice, 0)
+			}
+			if paymentMap[invoiceList[i].ContractRegisterId] == nil {
+				paymentMap[invoiceList[i].ContractRegisterId] = make([]*fms.ContractInvoice, 0)
+			}
+			if invoiceList[i].InvoiceType == fms.ContractInvoiceTypeMake {
+				invoiceMap[invoiceList[i].ContractRegisterId] = append(invoiceMap[invoiceList[i].ContractRegisterId], invoiceList[i])
+			}
+			if invoiceList[i].InvoiceType == fms.ContractInvoiceTypePay {
+				paymentMap[invoiceList[i].ContractRegisterId] = append(paymentMap[invoiceList[i].ContractRegisterId], invoiceList[i])
+				paymentIds = append(paymentIds, invoiceList[i].ContractInvoiceId)
+			}
+		}
+
+		// 合同套餐
+		contractServiceCond := `contract_register_id IN ?`
+		contractServicePars := make([]interface{}, 0)
+		contractServicePars = append(contractServicePars, registerIds)
+		contractServiceOB := new(fms.ContractService)
+		contractServiceList, e := contractServiceOB.List(contractServiceCond, contractServicePars)
+		if e != nil {
+			resp.FailMsg("获取失败", "获取合同套餐列表失败, Err:"+e.Error(), c)
+			return
+		}
+		contractServiceMap := make(map[int][]*fms.ContractService, 0)
+		servicesNameMap := make(map[int][]string, 0)
+		for i := range contractServiceList {
+			if contractServiceMap[contractServiceList[i].ContractRegisterId] == nil {
+				contractServiceMap[contractServiceList[i].ContractRegisterId] = make([]*fms.ContractService, 0)
+			}
+			contractServiceMap[contractServiceList[i].ContractRegisterId] = append(contractServiceMap[contractServiceList[i].ContractRegisterId], contractServiceList[i])
+			servicesNameMap[contractServiceList[i].ContractRegisterId] = append(servicesNameMap[contractServiceList[i].ContractRegisterId], contractServiceList[i].Title)
+		}
+
+		// 到款套餐分配
+		serviceAmountMap := make(map[int][]*fms.ContractPaymentServiceAmount, 0)
+		if len(paymentIds) > 0 {
+			serviceAmountCond := `contract_invoice_id IN ?`
+			serviceAmountPars := make([]interface{}, 0)
+			serviceAmountPars = append(serviceAmountPars, paymentIds)
+			serviceAmountOB := new(fms.ContractPaymentServiceAmount)
+			serviceAmountList, e := serviceAmountOB.List(serviceAmountCond, serviceAmountPars)
+			if e != nil {
+				resp.FailMsg("获取失败", "获取到款套餐分配列表失败, Err:"+e.Error(), c)
+				return
+			}
+			for i := range serviceAmountList {
+				if serviceAmountMap[serviceAmountList[i].ContractRegisterId] == nil {
+					serviceAmountMap[serviceAmountList[i].ContractRegisterId] = make([]*fms.ContractPaymentServiceAmount, 0)
+				}
+				serviceAmountMap[serviceAmountList[i].ContractRegisterId] = append(serviceAmountMap[serviceAmountList[i].ContractRegisterId], serviceAmountList[i])
+			}
+		}
+
+		// 整合响应数据
+		respList := make([]*fms.InvoicePaymentCensusItem, 0)
+		for i := range list {
+			v := new(fms.InvoicePaymentCensusItem)
+			v.ContractRegisterId = list[i].ContractRegisterId
+			v.CompanyName = list[i].CompanyName
+			v.NewCompany = list[i].NewCompany
+			v.StartDate = list[i].StartDate.Format(utils.FormatDate)
+			v.StartDate = list[i].StartDate.Format(utils.FormatDate)
+			svList := servicesNameMap[list[i].ContractRegisterId]
+			v.ServicesName = strings.Join(svList, ",")
+			// 格式化(合并)开票到款数据
+			v.InvoicePaymentList = fmsService.MergeInvoiceList2InvoicePaymentCensusInfo(invoiceMap[list[i].ContractRegisterId], paymentMap[list[i].ContractRegisterId],
+				contractServiceMap[list[i].ContractRegisterId], serviceAmountMap[list[i].ContractRegisterId])
+			respList = append(respList, v)
+		}
+
+		// 开票到款金额合计
+		amountTotalCond := `contract_register_id IN ?`
+		amountTotalPars := make([]interface{}, 0)
+		amountTotalPars = append(amountTotalPars, registerIds)
+		amountTotalList, e := fms.GetContractInvoiceAmountTotal(amountTotalCond, amountTotalPars)
+		if e != nil {
+			resp.FailMsg("获取失败", "获取开票到款金额合计失败, Err:"+e.Error(), c)
+			return
+		}
+		amountTotalMap := make(map[int]float64)
+		for i := range amountTotalList {
+			amountTotalMap[amountTotalList[i].InvoiceType] = amountTotalList[i].TotalAmount
+		}
+
+		results.DataList = respList
+		results.InvoiceTotal = amountTotalMap[fms.ContractInvoiceTypeMake]
+		results.PaymentTotal = amountTotalMap[fms.ContractInvoiceTypePay]
+	}
+
+	page.SetTotal(total)
+	baseData := new(base.BaseData)
+	baseData.SetPage(page)
+	baseData.SetList(results)
+	resp.OkData("获取成功", baseData, c)
+}

+ 98 - 0
controller/contract/payment.go

@@ -0,0 +1,98 @@
+package contract
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/fms_api/controller/resp"
+	"hongze/fms_api/global"
+	"hongze/fms_api/models/fms"
+	"hongze/fms_api/utils"
+	"time"
+)
+
+type PaymentController struct{}
+
+// UpdatePaymentPayType
+// @Title 修改付款方式
+// @Description 修改付款方式
+// @Param	request  body  fms.UpdatePaymentPayTypeReq  true "type json string"
+// @Success 200 string "操作成功"
+// @router /contract/payment/update_pay_type [post]
+func (ct *PaymentController) UpdatePaymentPayType(c *gin.Context) {
+	req := new(fms.UpdatePaymentPayTypeReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	ob := new(fms.ContractInvoice)
+	item, e := ob.Fetch(req.ContractPaymentId)
+	if e != nil {
+		if e == utils.ErrNoRow {
+			resp.Fail("到款登记不存在或已被删除", c)
+			return
+		}
+		resp.FailMsg("获取到款登记失败", "Err:"+e.Error(), c)
+		return
+	}
+
+	nowTime := time.Now().Local()
+	item.PayType = req.PayType
+	item.ModifyTime = nowTime
+	updateCols := []string{"PayType", "ModifyTime"}
+	if e = item.Update(updateCols); e != nil {
+		resp.FailMsg("操作失败", "更新到款登记失败, Err:"+e.Error(), c)
+		return
+	}
+	resp.Ok("操作成功", c)
+}
+
+// DistributePaymentServiceAmount
+// @Title 分配套餐金额
+// @Description 分配套餐金额
+// @Param	request  body  fms.DistributePaymentServiceAmountReq  true "type json string"
+// @Success 200 string "操作成功"
+// @router /contract/payment/distribute_service_amount [post]
+func (ct *PaymentController) DistributePaymentServiceAmount(c *gin.Context) {
+	req := new(fms.DistributePaymentServiceAmountReq)
+	err := c.ShouldBind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	addList := make([]*fms.ContractPaymentServiceAmount, 0)
+	for i := range req.List {
+		if req.List[i].Amount <= 0 {
+			continue
+		}
+		if req.List[i].ServiceTemplateId <= 0 {
+			resp.Fail("套餐信息有误", c)
+			return
+		}
+		v := &fms.ContractPaymentServiceAmount{
+			ContractRegisterId: req.ContractRegisterId,
+			ContractPaymentId:  req.ContractPaymentId,
+			ServiceTemplateId:  req.List[i].ServiceTemplateId,
+			Amount:             req.List[i].Amount,
+		}
+		v.Set()
+		addList = append(addList, v)
+	}
+	if e := fms.CreatePaymentServiceAmount(req.ContractRegisterId, req.ContractPaymentId, addList); e != nil {
+		resp.FailMsg("操作失败", "新增到款套餐金额失败, Err: "+e.Error(), c)
+		return
+	}
+	resp.Ok("操作成功", c)
+}

+ 11 - 1
controller/contract/register.go

@@ -676,6 +676,8 @@ func (rg *RegisterController) Invoice(c *gin.Context) {
 		resp.Fail("合同存在代付不允许添加开票/到款登记", c)
 		return
 	}
+	// 合同有效时长
+	dayDiff := item.EndDate.Sub(item.StartDate).Hours() / 24
 
 	noChangeInvoiceIds := make([]int, 0)
 	newInvoice := make([]*fms.ContractInvoice, 0)
@@ -725,7 +727,7 @@ func (rg *RegisterController) Invoice(c *gin.Context) {
 					Remark:             req.AmountList[i].Remark,
 				}
 				v.Set()
-				// 销售信息
+				// 开票登记-销售信息
 				if req.InvoiceType == fms.ContractInvoiceTypeMake {
 					sellerItem := sellerMap[req.AmountList[i].SellerId]
 					if sellerItem == nil {
@@ -735,7 +737,13 @@ func (rg *RegisterController) Invoice(c *gin.Context) {
 					v.SellerId = sellerItem.SellerId
 					v.SellerName = sellerItem.SellerName
 					v.SellerGroupId = sellerItem.GroupId
+					v.SellerGroupName = sellerItem.GroupName
 					v.SellerTeamId = sellerItem.TeamId
+					v.SellerTeamName = sellerItem.TeamName
+				}
+				// 到款登记-付款方式
+				if req.InvoiceType == fms.ContractInvoiceTypePay {
+					v.PayType = fmsService.CalculateContractPaymentType(req.AmountList[i].Amount, item.ContractAmount, dayDiff)
 				}
 				newInvoice = append(newInvoice, v)
 			}
@@ -1527,6 +1535,8 @@ func (rg *RegisterController) Import(c *gin.Context) {
 		serviceTempNameMap[serviceTempList[i].Title] = serviceTempList[i]
 	}
 
+	// TODO:导入开票信息时的销售信息以及导入到款信息时的付款方式信息
+
 	titleMap := make(map[int]string)
 	newIds := make([]int, 0)
 	contractTypeArr := []string{"0", "1", "2", "3"}

+ 18 - 0
controller/crm/company_seller.go

@@ -56,3 +56,21 @@ func (rg *CompanySellerController) List(c *gin.Context) {
 	}
 	resp.OkData("获取成功", list, c)
 }
+
+// GroupList
+// @Title 销售组别列表
+// @Description 销售组别列表
+// @Success 200 {object} crm.SysGroup
+// @router /crm/company_seller/group_list [get]
+func (rg *CompanySellerController) GroupList(c *gin.Context) {
+	groupOB := new(crm.SysGroup)
+	cond := `department_id = ? AND parent_id = 0`
+	pars := make([]interface{}, 0)
+	pars = append(pars, crm.SellerDepartmentId)
+	list, e := groupOB.List(cond, pars)
+	if e != nil {
+		resp.FailData("获取失败", "获取销售组别列表失败, Err:"+e.Error(), c)
+		return
+	}
+	resp.OkData("获取成功", list, c)
+}

+ 4 - 0
init_serve/router.go

@@ -40,5 +40,9 @@ func InitRouter() (r *gin.Engine) {
 	// 合同相关路由
 	contractGroup := rBase.Group("contract/")
 	routers.InitContract(contractGroup)
+
+	// 统计相关路由
+	censusGroup := rBase.Group("census/")
+	routers.InitCensus(censusGroup)
 	return
 }

+ 1 - 0
init_serve/task.go

@@ -8,5 +8,6 @@ func InitTask() {
 	// TODO:执行一次
 	fmt.Println("task start")
 	//fmsService.FixContractInvoiceSellerInfo()
+	//fmsService.FixContractInvoicePaymentType()
 	fmt.Println("task end")
 }

+ 7 - 0
models/fms/constants.go

@@ -27,6 +27,13 @@ const (
 	// 合同登记开票类型
 	ContractInvoiceTypeMake = 1 // 开票登记
 	ContractInvoiceTypePay  = 2 // 到款登记
+
+	// 到款登记付款方式
+	ContractPaymentPayTypeYear     = 1 // 年付
+	ContractPaymentPayTypeHalfYear = 2 // 半年付
+	ContractPaymentPayTypeQuarter  = 3 // 季付
+	ContractPaymentPayTypeOther    = 4 // 次付
+	ContractPaymentPayTypeAbnormal = 5 // 异常
 )
 
 var ContractStatusKeyNameMap = map[int]string{

+ 104 - 0
models/fms/contract_invoice.go

@@ -18,7 +18,10 @@ type ContractInvoice struct {
 	SellerId           int       `gorm:"column:seller_id" json:"seller_id" description:"销售ID"`
 	SellerName         string    `gorm:"column:seller_name" json:"seller_name" description:"销售名称"`
 	SellerGroupId      int       `gorm:"column:seller_group_id" json:"seller_group_id" description:"销售分组ID"`
+	SellerGroupName    string    `gorm:"column:seller_group_name" json:"seller_group_name" description:"销售分组名称"`
 	SellerTeamId       int       `gorm:"column:seller_team_id" json:"seller_team_id" description:"销售小组ID"`
+	SellerTeamName     string    `gorm:"column:seller_team_name" json:"seller_team_name" description:"销售小组名称"`
+	PayType            int       `gorm:"column:pay_type" json:"pay_type" description:"付款方式:0-无;1-年付;2-半年付;3-季付;4-次付;5-异常"`
 	AdminId            int       `gorm:"column:admin_id" json:"admin_id" description:"操作人ID"`
 	AdminName          string    `gorm:"column:admin_name" json:"admin_name" description:"操作人姓名"`
 	Remark             string    `gorm:"column:remark" json:"remark" description:"备注信息"`
@@ -40,6 +43,7 @@ type ContractInvoiceItem struct {
 	InvoiceDate        string  `gorm:"column:invoice_time" json:"invoice_time" description:"开票日期/到款月"`
 	SellerId           int     `gorm:"column:seller_id" json:"seller_id" description:"销售ID"`
 	SellerName         string  `gorm:"column:seller_name" json:"seller_name" description:"销售名称"`
+	PayType            int     `gorm:"column:pay_type" json:"pay_type" description:"付款方式:0-无;1-年付;2-半年付;3-季付;4-次付;5-异常"`
 	Remark             string  `gorm:"column:remark" json:"remark" description:"备注信息"`
 	CreateTime         string  `gorm:"column:create_time" json:"create_time" description:"创建时间"`
 }
@@ -59,6 +63,11 @@ func (c *ContractInvoice) Update(updateCols []string) (err error) {
 	return
 }
 
+func (c *ContractInvoice) Fetch(id int) (item *ContractInvoice, err error) {
+	err = global.DEFAULT_MYSQL.Model(c).Where("is_deleted = 0 AND contract_invoice_id = ?", id).First(&item).Error
+	return
+}
+
 func (c *ContractInvoice) List(condition string, pars []interface{}, orderRule string) (list []*ContractInvoice, err error) {
 	list = make([]*ContractInvoice, 0)
 	query := global.DEFAULT_MYSQL.Model(c).
@@ -158,6 +167,7 @@ func formatContractInvoice2ItemList(list []*ContractInvoice) (itemList []*Contra
 			InvoiceDate:        utils.TimeTransferString(utils.FormatDate, list[i].InvoiceDate),
 			SellerId:           list[i].SellerId,
 			SellerName:         list[i].SellerName,
+			PayType:            list[i].PayType,
 			Remark:             list[i].Remark,
 			CreateTime:         utils.TimeTransferString(utils.FormatDateTime, list[i].CreateTime),
 		})
@@ -216,3 +226,97 @@ func DeleteContractInvoicesByRegisterId(registerId int) (err error) {
 	err = global.DEFAULT_MYSQL.Exec(sql, registerId).Error
 	return
 }
+
+// InvoicePaymentCensusListReq 商品到款统计列表请求体
+type InvoicePaymentCensusListReq struct {
+	Keyword     string `json:"keyword" form:"keyword" binding:"omitempty" description:"关键词"`
+	SellGroupId int    `json:"sell_group_id" form:"sell_group_id" description:"销售组别ID"`
+	ServiceType int    `json:"service_type" form:"service_type" description:"套餐类型"`
+	StartDate   string `json:"start_date" form:"start_date" binding:"omitempty,datetime=2006-01-02" description:"开始日期"`
+	EndDate     string `json:"end_date" form:"end_date" binding:"omitempty,datetime=2006-01-02" description:"结束日期"`
+	TimeType    int    `json:"time_type" form:"time_type" description:"时间类型: 1-开票时间; 2-到款时间"`
+	HasInvoice  int    `json:"has_invoice" form:"has_invoice" description:"已开票"`
+	HasPayment  int    `json:"has_payment" form:"has_payment" description:"已到款"`
+	base.PageReq
+}
+
+// GetInvoicePaymentCensusPageList 获取商品到款统计列表-分页
+func GetInvoicePaymentCensusPageList(page base.IPage, condition string, pars []interface{}) (count int64, results []*ContractRegister, err error) {
+	query := global.DEFAULT_MYSQL.Table("contract_invoice AS a").
+		Select("b.*").
+		Joins("JOIN contract_register AS b ON a.contract_register_id = b.contract_register_id").
+		Where("a.is_deleted = 0 AND b.is_deleted = 0").
+		Where(condition, pars...).
+		Group("b.contract_register_id")
+	if len(page.GetOrderItemsString()) > 0 {
+		query = query.Order(page.GetOrderItemsString())
+	}
+	err = query.Limit(int(page.GetPageSize())).Offset(int(page.Offset())).Find(&results).Error
+	if err != nil {
+		return
+	}
+	// 计数
+	queryCount := global.DEFAULT_MYSQL.Table("contract_invoice AS a").
+		Joins("JOIN contract_register AS b ON a.contract_register_id = b.contract_register_id").
+		Where("a.is_deleted = 0 AND b.is_deleted = 0").
+		Where(condition, pars...).
+		Group("b.contract_register_id")
+	queryCount.Count(&count)
+	return
+}
+
+// InvoicePaymentCensusResp 商品到款统计响应体
+type InvoicePaymentCensusResp struct {
+	DataList     []*InvoicePaymentCensusItem `json:"data_list"`
+	InvoiceTotal float64                     `json:"invoice_total" description:"开票总金额"`
+	PaymentTotal float64                     `json:"payment_total" description:"到款总金额"`
+}
+
+// InvoicePaymentCensusItem 商品到款统计信息
+type InvoicePaymentCensusItem struct {
+	ContractRegisterId int                         `json:"contract_register_id" description:"登记ID"`
+	CompanyName        string                      `json:"company_name" description:"客户名称"`
+	NewCompany         int                         `json:"new_company" description:"是否为新客户: 0-否; 1-是"`
+	StartDate          string                      `json:"start_date" description:"合同开始日期"`
+	EndDate            string                      `json:"end_date" description:"合同结束日期"`
+	ServicesName       string                      `json:"services_name" description:"套餐信息字符串拼接"`
+	InvoicePaymentList []*InvoicePaymentCensusInfo `json:"invoice_payment_list" description:"开票到款列表"`
+}
+
+// InvoicePaymentCensusInfo 开票到款统计信息
+type InvoicePaymentCensusInfo struct {
+	InvoiceId         int                                 `json:"invoice_id" description:"开票ID"`
+	InvoiceDate       string                              `json:"invoice_time" description:"开票日期"`
+	InvoiceAmount     float64                             `json:"invoice_amount" description:"开票金额"`
+	SellerId          int                                 `json:"seller_id" description:"销售ID"`
+	SellerName        string                              `json:"seller_name" description:"销售名称"`
+	SellerGroupId     int                                 `json:"seller_group_id" description:"销售组别ID"`
+	SellerGroupName   string                              `json:"seller_group_name" description:"销售组别名称"`
+	PaymentId         int                                 `json:"payment_id" description:"到款ID"`
+	PaymentDate       string                              `json:"payment_date" description:"到款日期"`
+	PaymentAmount     float64                             `json:"payment_amount" description:"到款金额"`
+	PayType           int                                 `json:"pay_type" description:"付款方式:0-无;1-年付;2-半年付;3-季付;4-次付;5-异常"`
+	ServiceAmountList []*ContractPaymentServiceAmountItem `json:"service_amount_list" description:"到款套餐金额分配信息"`
+}
+
+// ContractInvoiceAmountTotal 开票到款金额合计信息
+type ContractInvoiceAmountTotal struct {
+	InvoiceType int     `json:"invoice_type" description:"类型: 1-开票; 2-到款"`
+	TotalAmount float64 `json:"total_amount" description:"金额合计"`
+}
+
+// GetContractInvoiceAmountTotal 获取开票到款金额合计信息
+func GetContractInvoiceAmountTotal(condition string, pars []interface{}) (results []*ContractInvoiceAmountTotal, err error) {
+	query := global.DEFAULT_MYSQL.Table("contract_invoice").
+		Select("invoice_type, SUM(amount) AS total_amount").
+		Where(condition, pars...).
+		Group("invoice_type")
+	err = query.Find(&results).Error
+	return
+}
+
+// UpdatePaymentPayTypeReq 到款登记-修改付款方式请求体
+type UpdatePaymentPayTypeReq struct {
+	ContractPaymentId int `json:"contract_payment_id" binding:"required,gte=1" description:"到款登记ID"`
+	PayType           int `json:"pay_type" binding:"oneof=1 2 3 4" description:"付款方式: 1-年付; 2-半年付; 3-季付; 4-次付"`
+}

+ 90 - 0
models/fms/contract_payment_service_amount.go

@@ -0,0 +1,90 @@
+package fms
+
+import (
+	"hongze/fms_api/global"
+	"hongze/fms_api/models/base"
+)
+
+// ContractPaymentServiceAmount 到款登记-套餐金额分配表
+type ContractPaymentServiceAmount struct {
+	ContractPaymentServiceAmountId int     `gorm:"primaryKey;column:contract_payment_service_amount_id" json:"contract_payment_service_amount_id"`
+	ContractRegisterId             int     `gorm:"column:contract_register_id" json:"contract_register_id" description:"合同登记ID"`
+	ContractPaymentId              int     `gorm:"column:contract_payment_id" json:"contract_payment_id" description:"到款登记ID"`
+	ServiceTemplateId              int     `gorm:"column:service_template_id" json:"service_template_id" description:"套餐ID"`
+	Amount                         float64 `gorm:"column:amount" json:"amount" description:"分配金额"`
+	IsDeleted                      int     `gorm:"column:is_deleted" json:"is_deleted" description:"是否已删除: 0-正常; 1-已删除"`
+	base.TimeBase
+}
+
+func (c *ContractPaymentServiceAmount) TableName() string {
+	return "contract_payment_service_amount"
+}
+
+func (c *ContractPaymentServiceAmount) Create() (err error) {
+	err = global.DEFAULT_MYSQL.Create(c).Error
+	return
+}
+
+func (c *ContractPaymentServiceAmount) AddInBatches(list []*ContractPaymentServiceAmount) (err error) {
+	err = global.DEFAULT_MYSQL.CreateInBatches(list, len(list)).Error
+	return
+}
+
+func (c *ContractPaymentServiceAmount) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(c).Select(updateCols).Updates(c).Error
+	return
+}
+
+func (c *ContractPaymentServiceAmount) List(condition string, pars []interface{}) (list []*ContractPaymentServiceAmount, err error) {
+	list = make([]*ContractPaymentServiceAmount, 0)
+	err = global.DEFAULT_MYSQL.Model(c).
+		Where("is_deleted = 0").
+		Where(condition, pars...).
+		Find(&list).Error
+	return
+}
+
+// ContractPaymentServiceAmountItem 到款套餐分配信息
+type ContractPaymentServiceAmountItem struct {
+	ContractPaymentServiceAmountId int     `json:"contract_payment_service_amount_id"`
+	ContractPaymentId              int     `json:"contract_payment_id" description:"到款登记ID"`
+	ServiceTemplateId              int     `json:"service_template_id" description:"套餐ID"`
+	ServiceTemplateName            string  `json:"service_template_name"`
+	Amount                         float64 `json:"amount" description:"分配金额"`
+}
+
+// DistributePaymentServiceAmountReq 到款登记-分配套餐金额请求体
+type DistributePaymentServiceAmountReq struct {
+	ContractRegisterId int                                   `json:"contract_register_id" binding:"required,gte=1" description:"合同登记ID"`
+	ContractPaymentId  int                                   `json:"contract_payment_id" binding:"required,gte=1" description:"到款登记ID"`
+	List               []*DistributePaymentServiceAmountItem `json:"list"`
+}
+
+// DistributePaymentServiceAmountItem 到款登记-分配套餐金额列表信息
+type DistributePaymentServiceAmountItem struct {
+	ContractPaymentServiceAmountId int     `json:"contract_payment_service_amount_id"`
+	ServiceTemplateId              int     `json:"service_template_id" description:"套餐ID"`
+	Amount                         float64 `json:"amount" description:"分配金额"`
+}
+
+// CreatePaymentServiceAmount 分配到款套餐金额
+func CreatePaymentServiceAmount(registerId, payId int, addList []*ContractPaymentServiceAmount) (err error) {
+	tx := global.DEFAULT_MYSQL.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 删除原分配信息
+	sql := `DELETE FROM contract_payment_service_amount WHERE contract_register_id = ? AND contract_payment_id = ?`
+	tx.Exec(sql, registerId, payId)
+
+	// 新增分配信息
+	if len(addList) > 0 {
+		tx.CreateInBatches(addList, len(addList))
+	}
+	return
+}

+ 14 - 0
routers/census.go

@@ -0,0 +1,14 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/fms_api/controller/census"
+	"hongze/fms_api/middleware"
+)
+
+func InitCensus(rg *gin.RouterGroup) {
+	// 商品到款统计
+	inv := new(census.InvoicePaymentController)
+	invGroup := rg.Group("invoice_payment/").Use(middleware.Token())
+	invGroup.GET("list", inv.List)
+}

+ 6 - 0
routers/contract.go

@@ -27,4 +27,10 @@ func InitContract(rg *gin.RouterGroup) {
 	sr := new(contract.ServiceController)
 	srGroup := rg.Group("service/").Use(middleware.Token())
 	srGroup.GET("list", sr.List)
+
+	// 到款登记
+	pay := new(contract.PaymentController)
+	payGroup := rg.Group("payment/").Use(middleware.Token())
+	payGroup.POST("update_pay_type", pay.UpdatePaymentPayType)
+	payGroup.POST("distribute_service_amount", pay.DistributePaymentServiceAmount)
 }

+ 1 - 0
routers/crm.go

@@ -18,4 +18,5 @@ func InitCrm(rg *gin.RouterGroup) {
 	sl := new(crm.CompanySellerController)
 	slGroup := rg.Group("company_seller/").Use(middleware.Token())
 	slGroup.GET("list", sl.List)
+	slGroup.GET("group_list", sl.GroupList)
 }

+ 42 - 1
services/fms/contract_register.go

@@ -281,7 +281,7 @@ func FixContractInvoiceSellerInfo() {
 		fmt.Println("获取合同登记列表失败, Err: " + e.Error())
 		return
 	}
-	invUpCols := []string{"SellerId", "SellerName", "SellerGroupId", "SellerTeamId"}
+	invUpCols := []string{"SellerId", "SellerName", "SellerGroupId", "SellerGroupName", "SellerTeamId", "SellerTeamName"}
 	fmt.Println("start 修复")
 	for i := range registerList {
 		registerId := registerList[i].ContractRegisterId
@@ -305,7 +305,9 @@ func FixContractInvoiceSellerInfo() {
 			v.SellerId = sellerItem.SellerId
 			v.SellerName = sellerItem.SellerName
 			v.SellerGroupId = sellerItem.GroupId
+			v.SellerGroupName = sellerItem.GroupName
 			v.SellerTeamId = sellerItem.TeamId
+			v.SellerTeamName = sellerItem.TeamName
 			if e = v.Update(invUpCols); e != nil {
 				fmt.Println("更新开票登记失败, Err: " + e.Error())
 				return
@@ -316,3 +318,42 @@ func FixContractInvoiceSellerInfo() {
 	fmt.Println("end 修复")
 	return
 }
+
+// FixContractInvoicePaymentType 修复历史到款登记付款方式(一次性)
+func FixContractInvoicePaymentType() {
+	registerCond := ``
+	registerPars := make([]interface{}, 0)
+	registerOB := new(fms.ContractRegister)
+	registerList, e := registerOB.List(registerCond, registerPars)
+	if e != nil {
+		fmt.Println("获取合同登记列表失败, Err: " + e.Error())
+		return
+	}
+	invUpCols := []string{"PayType"}
+	fmt.Println("start 修复")
+	for i := range registerList {
+		registerId := registerList[i].ContractRegisterId
+
+		invOB := new(fms.ContractInvoice)
+		invCond := `invoice_type = 2 AND contract_register_id = ?`
+		invPars := make([]interface{}, 0)
+		invPars = append(invPars, registerId)
+		invList, e := invOB.List(invCond, invPars, "")
+		if e != nil {
+			fmt.Println("获取到款登记列表失败, Err: " + e.Error())
+			return
+		}
+
+		dayDiff := registerList[i].EndDate.Sub(registerList[i].StartDate).Hours() / 24
+		for _, v := range invList {
+			v.PayType = CalculateContractPaymentType(v.Amount, registerList[i].ContractAmount, dayDiff)
+			if e = v.Update(invUpCols); e != nil {
+				fmt.Println("更新到款登记付款方式失败, Err: " + e.Error())
+				return
+			}
+			fmt.Printf("ID-%d 修复成功\n", v.ContractInvoiceId)
+		}
+	}
+	fmt.Println("end 修复")
+	return
+}

+ 154 - 0
services/fms/invoice_payment.go

@@ -0,0 +1,154 @@
+package fms
+
+import (
+	"fmt"
+	"github.com/shopspring/decimal"
+	"hongze/fms_api/models/fms"
+	"hongze/fms_api/utils"
+)
+
+// CalculateContractPaymentType 计算到款登记付款方式
+// 计算公式: 到款金额 / ( 合同金额 / round( (合同结束日期 - 合同开始日期) / 365) )
+func CalculateContractPaymentType(paymentAmount, contractAmount, dayDiff float64) (payEnum int) {
+	if paymentAmount <= 0 || contractAmount <= 0 || dayDiff <= 0 {
+		return
+	}
+
+	// 年份四舍五入
+	days := decimal.NewFromFloat(dayDiff)
+	yearDays := decimal.NewFromFloat(365)
+	yearRound := days.DivRound(yearDays, 2)
+	if yearRound.IsZero() {
+		return
+	}
+	years := yearRound.Add(decimal.NewFromFloat(0.5)).Floor()
+	if years.IsZero() {
+		return
+	}
+	// 分母
+	contractDec := decimal.NewFromFloat(contractAmount)
+	contractDeno := contractDec.DivRound(years, 2)
+	if contractDeno.IsZero() {
+		return
+	}
+	paymentDec := decimal.NewFromFloat(paymentAmount)
+	// 结果
+	resultDec := paymentDec.DivRound(contractDeno, 2)
+
+	// 标准比例
+	yearPay := decimal.NewFromFloat(1)
+	halfYearPay := decimal.NewFromFloat(0.5)
+	quarterPay := decimal.NewFromFloat(0.25)
+	zeroPay := decimal.NewFromFloat(0)
+
+	payEnum = fms.ContractPaymentPayTypeOther
+	// 异常
+	if yearPay.LessThan(resultDec) || resultDec.LessThanOrEqual(zeroPay) {
+		payEnum = fms.ContractPaymentPayTypeAbnormal
+		return
+	}
+	// 年付
+	if resultDec.Equal(yearPay) {
+		payEnum = fms.ContractPaymentPayTypeYear
+		return
+	}
+	// 半年付
+	if resultDec.Equal(halfYearPay) {
+		payEnum = fms.ContractPaymentPayTypeHalfYear
+		return
+	}
+	// 季付
+	if resultDec.Equal(quarterPay) {
+		payEnum = fms.ContractPaymentPayTypeQuarter
+		return
+	}
+	return
+}
+
+// MergeInvoiceList2InvoicePaymentCensusInfo 合同登记-合并开票到款列表
+func MergeInvoiceList2InvoicePaymentCensusInfo(invoiceList []*fms.ContractInvoice, paymentList []*fms.ContractInvoice, serviceList []*fms.ContractService, serviceAmountList []*fms.ContractPaymentServiceAmount) (mergeList []*fms.InvoicePaymentCensusInfo) {
+	invoiceLen := len(invoiceList)
+	paymentLen := len(paymentList)
+	if invoiceLen == 0 && paymentLen == 0 {
+		return
+	}
+
+	// 到款套餐分配信息
+	amountMap := make(map[string]*fms.ContractPaymentServiceAmount)
+	for i := range serviceAmountList {
+		k := fmt.Sprintf("%d-%d", serviceAmountList[i].ContractPaymentId, serviceAmountList[i].ServiceTemplateId)
+		amountMap[k] = serviceAmountList[i]
+	}
+	serviceAmountMap := make(map[int][]*fms.ContractPaymentServiceAmountItem)
+	for i := range paymentList {
+		l := make([]*fms.ContractPaymentServiceAmountItem, 0)
+		for ii := range serviceList {
+			v := new(fms.ContractPaymentServiceAmountItem)
+			v.ServiceTemplateId = serviceList[ii].ServiceTemplateId
+			v.ServiceTemplateName = serviceList[ii].Title
+			k := fmt.Sprintf("%d-%d", paymentList[i].ContractInvoiceId, serviceList[ii].ServiceTemplateId)
+			a := amountMap[k]
+			if a != nil {
+				v.ContractPaymentServiceAmountId = a.ContractPaymentServiceAmountId
+				v.ContractPaymentId = a.ContractPaymentId
+				v.Amount = a.Amount
+			}
+			l = append(l, v)
+		}
+		serviceAmountMap[paymentList[i].ContractInvoiceId] = l
+	}
+
+	// 开票和到款列表, 条数多的为最后的合并条数
+	mergeList = make([]*fms.InvoicePaymentCensusInfo, 0)
+	if invoiceLen >= paymentLen {
+		for i := range invoiceList {
+			v := new(fms.InvoicePaymentCensusInfo)
+			v.InvoiceId = invoiceList[i].ContractInvoiceId
+			v.InvoiceDate = invoiceList[i].InvoiceDate.Format(utils.FormatDate)
+			v.InvoiceAmount = invoiceList[i].Amount
+			v.SellerId = invoiceList[i].SellerId
+			v.SellerName = invoiceList[i].SellerName
+			v.SellerGroupName = invoiceList[i].SellerGroupName
+			// 直接取对应键的到款列表
+			if i+1 <= paymentLen {
+				payItem := paymentList[i]
+				if payItem != nil {
+					v.PaymentId = payItem.ContractInvoiceId
+					v.PaymentDate = payItem.InvoiceDate.Format(utils.FormatDate)
+					v.PaymentAmount = payItem.Amount
+					v.PayType = payItem.PayType
+					v.ServiceAmountList = serviceAmountMap[payItem.ContractInvoiceId]
+				}
+			}
+			mergeList = append(mergeList, v)
+		}
+		return
+	}
+
+	// 到款多于开票
+	if paymentLen > invoiceLen {
+		for i := range paymentList {
+			v := new(fms.InvoicePaymentCensusInfo)
+			v.PaymentId = paymentList[i].ContractInvoiceId
+			v.PaymentDate = paymentList[i].InvoiceDate.Format(utils.FormatDate)
+			v.PaymentAmount = paymentList[i].Amount
+			v.PayType = paymentList[i].PayType
+			v.ServiceAmountList = serviceAmountMap[paymentList[i].ContractInvoiceId]
+			// 直接取对应键的开票
+			if i+1 <= invoiceLen {
+				invoiceItem := invoiceList[i]
+				if invoiceItem != nil {
+					v.InvoiceId = invoiceItem.ContractInvoiceId
+					v.InvoiceDate = invoiceItem.InvoiceDate.Format(utils.FormatDate)
+					v.InvoiceAmount = invoiceItem.Amount
+					v.SellerId = invoiceItem.SellerId
+					v.SellerName = invoiceItem.SellerName
+					v.SellerGroupName = invoiceList[i].SellerGroupName
+				}
+			}
+			mergeList = append(mergeList, v)
+		}
+		return
+	}
+	return
+}