浏览代码

fix:合同审批后生成的pdf调整为word转pdf

Roc 3 年之前
父节点
当前提交
fef8889a98
共有 4 个文件被更改,包括 918 次插入20 次删除
  1. 1 0
      models/tables/contract_template/contract_template.go
  2. 24 6
      services/contract/contract_approval.go
  3. 884 14
      services/word.go
  4. 9 0
      utils/config.go

+ 1 - 0
models/tables/contract_template/contract_template.go

@@ -12,6 +12,7 @@ type ContractTemplate struct {
 	Url                string    `description:"模板地址"`
 	Html               string    `description:"html模板"`
 	PdfHtml            string    `description:"生成pdf的html模板"`
+	WordConfig         string    `description:"生成word的json配置"`
 	CreateTime         time.Time `description:"创建时间"`
 }
 

+ 24 - 6
services/contract/contract_approval.go

@@ -700,21 +700,39 @@ func AfterApproved(contractId int) (err error) {
 
 	//pdf生成并保存
 	{
-		//获取合同的html模板信息
-		contractHtml, tmpErr := services.GetHtmlByContractDetail(contractDetail, "pdf")
+		////获取合同的html模板信息
+		//contractHtml, tmpErr := services.GetHtmlByContractDetail(contractDetail, "pdf")
+		//if tmpErr != nil {
+		//	err = tmpErr
+		//	return
+		//}
+		//
+		////生成pdf
+		//pdfPath := fmt.Sprint("./static/系统生成合同_", contractId, ".pdf")
+		//tmpErr = services.Html2Pdf(contractHtml, pdfPath)
+		//if tmpErr != nil {
+		//	err = tmpErr
+		//	return
+		//}
+		//
+		//defer os.Remove(pdfPath)
+
+		//生成word
+		wordPath := fmt.Sprint("./static/word/系统生成合同_", contractDetail.ContractId, ".docx")
+		tmpErr := services.GenerateWordV2(contractDetail, wordPath)
 		if tmpErr != nil {
 			err = tmpErr
 			return
 		}
+		defer os.Remove(wordPath)
 
-		//生成pdf
-		pdfPath := fmt.Sprint("./static/系统生成合同_", contractId, ".pdf")
-		tmpErr = services.Html2Pdf(contractHtml, pdfPath)
+		//word转pdf
+		converterType := "pdf"
+		pdfPath, tmpErr := services.FuncDocs2Pdf(utils.LibreOfficePath, wordPath, "./static/word", converterType)
 		if tmpErr != nil {
 			err = tmpErr
 			return
 		}
-
 		defer os.Remove(pdfPath)
 
 		//randStr := utils.GetRandStringNoSpecialChar(28)

+ 884 - 14
services/word.go

@@ -19,9 +19,13 @@ import (
 	"hongze/hongze_mobile_admin/models/tables/contract_template"
 	"hongze/hongze_mobile_admin/utils"
 	"html/template"
+	"os"
+	"os/exec"
+	"path"
 	"reflect"
 	"strconv"
 	"strings"
+	"time"
 )
 
 type TableData struct {
@@ -569,21 +573,27 @@ func GetHtmlByContractDetail(contractDetail *contract.ContractDetail, htmlType s
 
 	//合同有效期
 	{
-		//合同结束日期与合同开始日期的时间差(小时差)
-		newDecimal := decimal.NewFromFloat(contractDetail.EndDate.Sub(contractDetail.StartDate).Hours())
-		//分母为365天 * 24 小时
-		newDecimal2 := decimal.NewFromInt(24 * 365)
-		//计算出来相差多少年,保留一位小数(四舍五入)
-		numYearDecimal := newDecimal.Div(newDecimal2).Round(1)
-		//定义最小年份差,不能小于0.1年
-		minDecimal := decimal.NewFromFloat(0.1)
-		//如果计算出来的年份差小于0.1年,那么该年份差就赋值 0.1年
-		if numYearDecimal.LessThan(minDecimal) {
-			numYearDecimal = minDecimal
+		////合同结束日期与合同开始日期的时间差(小时差)
+		//newDecimal := decimal.NewFromFloat(contractDetail.EndDate.Sub(contractDetail.StartDate).Hours())
+		////分母为365天 * 24 小时
+		//newDecimal2 := decimal.NewFromInt(24 * 365)
+		////计算出来相差多少年,保留一位小数(四舍五入)
+		//numYearDecimal := newDecimal.Div(newDecimal2).Round(1)
+		////定义最小年份差,不能小于0.1年
+		//minDecimal := decimal.NewFromFloat(0.1)
+		////如果计算出来的年份差小于0.1年,那么该年份差就赋值 0.1年
+		//if numYearDecimal.LessThan(minDecimal) {
+		//	numYearDecimal = minDecimal
+		//}
+		////合同有效期
+		//data.NumYear = numYearDecimal.String()
+
+		tmpPrintContent, tmpErr := CalculationDate(contractDetail.StartDate, contractDetail.EndDate)
+		if tmpErr != nil {
+			err = tmpErr
+			return
 		}
-
-		//合同有效期
-		data.NumYear = numYearDecimal.String()
+		data.NumYear = tmpPrintContent
 	}
 
 	//合同金额
@@ -1056,6 +1066,726 @@ func getColList(item *contract_service_detail.ContractServiceDetail) (cellList [
 	return
 }
 
+type WordElement struct {
+	ElementType  string        `json:"element_type" description:"元素类型"`
+	ElementName  string        `json:"element_name" description:"元素名称"`
+	RelationName string        `json:"relation_name" description:"关联元素名称"`
+	Content      string        `json:"content" description:"元素内容"`
+	Background   string        `json:"background" description:"背景色"`
+	IsBold       bool          `json:"is_bold" description:"是否加粗显示"`
+	TextAlign    string        `json:"text_align" description:"对齐方式"`
+	FontSize     float64       `json:"font_size" description:"字体大小"`
+	ElementList  []WordElement `json:"list" description:"子元素"`
+}
+
+// GenerateWordV2 生成word
+func GenerateWordV2(contractDetail *contract.ContractDetail, wordPath string) (err error) {
+	contractTemplate, err := contract_template.GetContractTemplateByTemplateId(contractDetail.TemplateId)
+	if err != nil {
+		return
+	}
+	jsonStr := contractTemplate.WordConfig
+
+	var contractData []WordElement
+	err = json.Unmarshal([]byte(jsonStr), &contractData)
+	if err != nil {
+		fmt.Println("json字符串解析失败,ERR:", err)
+		return
+	}
+
+	doc := document.New()
+
+	//word的属性设置,类型,作者之类的
+	cp := doc.CoreProperties
+	// And change them as well
+	cp.SetTitle("弘则弥道(上海)投资咨询有限公司 & 研究服务合同")
+	cp.SetAuthor("弘则弥道(上海)投资咨询有限公司")
+	cp.SetCategory("合同")
+	//cp.SetContentStatus("Draft")
+	cp.SetLastModifiedBy("弘则弥道(上海)投资咨询有限公司")
+	cp.SetCreated(time.Now())
+	cp.SetModified(time.Now())
+	cp.SetDescription("弘则弥道(上海)投资咨询有限公司 研究服务合同")
+	cp.SetLanguage("中文")
+
+	for _, data := range contractData {
+		fontSize := data.FontSize
+		if fontSize <= 0 {
+			fontSize = 15
+		}
+
+		printContent := ``
+		if data.ElementName == "services" {
+			tableTitleSlice := make([]string, 0)
+			title := ""
+			if contractDetail.ProductId == 1 {
+				title = "依照《【弘则研究】FICC客户客户服务列表2021》中"
+			} else {
+				title = "依照《【弘则研究】私募客户客户服务列表2021》中"
+			}
+			TableDataListSlice := make([]TableData, 0)
+			//for i := len(contractDetail.Service) - 1; i >= 0; i-- {
+			for i := 0; i < len(contractDetail.Service); i++ {
+				//表格数据
+				var tableDataList TableData
+
+				item := contractDetail.Service[i]
+				//表头备注信息
+				tableTitleSlice = append(tableTitleSlice, item.Title)
+				//表格数据
+				if item.HasDetail == "是" && len(item.DetailList) > 0 {
+					//表格每行数据切片
+					tableRowList := make([]TableRow, 0)
+
+					//遍历获取table行数据
+					for j := 0; j < len(item.DetailList); j++ {
+						//列数据样式初始化
+						isBold := false
+						backgrandColor := ""
+						fontSize := 10.0
+
+						//表头数据样式
+						if j == 0 {
+							isBold = true
+							backgrandColor = "gray_2"
+							fontSize = 12.0
+						}
+
+						//获取每一列的数据
+						tmpCellList, colErr := getColList(item.DetailList[j])
+						if colErr != nil {
+							err = colErr
+							return
+						}
+
+						//定义生成table列数据切片
+						tableCelList := make([]TableCel, 0)
+						lenCell := len(tmpCellList)
+						for k := 0; k < len(tmpCellList); k++ {
+							//计算出来每一列的宽度占比 start
+							//总宽度
+							newDecimal := decimal.NewFromFloat(100)
+							//总列数
+							newDecimal2 := decimal.NewFromInt(int64(lenCell))
+							//计算出来每一列的宽度占比(四舍五入)
+							widthPercent, _ := newDecimal.Div(newDecimal2).Round(3).Float64()
+							//if !ok {
+							//	err = errors.New("word普通数据表格宽度百分比计算失败")
+							//	return
+							//}
+							//计算出来每一列的宽度占比 end
+
+							tableCel := TableCel{
+								Value:     tmpCellList[k],
+								TextAlign: "center",
+								//ColumnSpan   int     `json:"column_span";description:"需要合同的列数量"`
+								//IsMerged     bool    `json:"is_merged";description:"是否需要上下行合并"`
+								Background:   backgrandColor,
+								IsBold:       isBold,
+								FontSize:     fontSize,
+								WidthPercent: widthPercent,
+							}
+							tableCelList = append(tableCelList, tableCel)
+						}
+
+						//将每行数据插入到table行数据切片之中
+						tableRow := TableRow{
+							RowList: tableCelList,
+						}
+						tableRowList = append(tableRowList, tableRow)
+					}
+					//赋值table表格数据
+					tableDataList.List = tableRowList
+				} else {
+					//获取预设的表格数据
+					contractServiceTemplate, tmpErr := contract_service_template.GetContractServiceTemplateById(item.ServiceTemplateId)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+
+					//赋值table表格数据
+					jsonStr := contractServiceTemplate.TableValue
+					err = json.Unmarshal([]byte(jsonStr), &tableDataList)
+					if err != nil {
+						return
+					}
+				}
+
+				//往word中添加表格数据
+				TableDataListSlice = append(TableDataListSlice, tableDataList)
+			}
+
+			//表格标题
+			titleStr := strings.Join(tableTitleSlice, "、")
+			title += titleStr + "的服务内容,详细如下:"
+
+			//开始一个新的段落
+			headerPar := doc.AddParagraph()
+			headerParPro := headerPar.Properties()
+			//headerParPro.AddTabStop()
+			textAlign := getTextAlignConf(data.TextAlign)
+			headerParPro.SetAlignment(textAlign)
+			//if data.ElementType == "column" {
+			//	headerParPro.SetAlignment(wml.ST_JcBoth)
+			//}
+
+			headerRun := headerPar.AddRun()
+			headerRunPro := headerRun.Properties()
+			headerRunPro.SetBold(data.IsBold)
+			headerRunPro.SetSize(measurement.Distance(fontSize * measurement.Point))
+			headerRunPro.SetFontFamily("宋体")
+			//headerRunPro.SetKerning(measurement.Distance(2 * fontSize * measurement.Point))
+			headerRun.AddText(title)
+
+			//生成表格
+			for _, tableDataList := range TableDataListSlice {
+				tmpErr := addTableV2(tableDataList, doc)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+			}
+			continue
+		} else {
+			isPrint, tmpPrintContent, tmpErr := getPrintData(data, contractDetail)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			if isPrint == false {
+				continue
+			}
+			printContent = tmpPrintContent
+		}
+		//分栏的宽度
+		printContentRune := []rune(printContent)
+		strLen := len(printContentRune)
+
+		addTabNum := 0
+		printContentList := make([]map[int]string, 0)
+		firstLen := 17
+		secondLen := 14
+		if data.ElementType == "column" {
+			if strLen > firstLen {
+				maxLine := ((strLen - firstLen) / secondLen) + 1
+				for i := 0; i < maxLine; i++ {
+					printContentMap := make(map[int]string)
+					startIndex := secondLen*i + firstLen
+					endIndex := secondLen*(i+1) + firstLen
+					if endIndex > strLen {
+						endIndex = strLen
+					}
+					tmpPrintContent := string(printContentRune[startIndex:endIndex])
+					printContentMap[0] = tmpPrintContent
+					printContentList = append(printContentList, printContentMap)
+				}
+				printContent = string(printContentRune[:firstLen])
+				strLen = firstLen
+
+				//重新计算宽度
+				//width = fontSize * float64(strLen)
+				//addTabNum = 3
+			} else {
+				addTabNum = firstLen - strLen
+			}
+			addTabNum += 3
+		}
+
+		//开始一个新的段落
+		headerPar := doc.AddParagraph()
+		headerParPro := headerPar.Properties()
+
+		headerParPro.Spacing().SetLineSpacing(measurement.Distance(1.5*fontSize*measurement.Point), wml.ST_LineSpacingRuleAuto)
+		//headerParPro.AddTabStop()
+		textAlign := getTextAlignConf(data.TextAlign)
+		headerParPro.SetAlignment(textAlign)
+		//if data.ElementType == "column" {
+		//	headerParPro.SetAlignment(wml.ST_JcBoth)
+		//}
+
+		headerRun := headerPar.AddRun()
+		headerRunPro := headerRun.Properties()
+		headerRunPro.SetBold(data.IsBold)
+		headerRunPro.SetSize(measurement.Distance(fontSize * measurement.Point))
+		headerRunPro.SetFontFamily("宋体")
+		//headerRunPro.SetKerning(measurement.Distance(2 * fontSize * measurement.Point))
+		headerRun.AddText(printContent)
+
+		for _, text := range data.ElementList {
+			if text.ElementName == "services" {
+				tableTitleSlice := make([]string, 0)
+				title := ""
+				if contractDetail.ProductId == 1 {
+					title = "依照《【弘则研究】FICC客户客户服务列表2021》中 "
+				} else {
+					title = "依照《【弘则研究】私募客户客户服务列表2021》中 "
+				}
+				TableDataListSlice := make([]TableData, 0)
+				for i := len(contractDetail.Service) - 1; i >= 0; i-- {
+					//表格数据
+					var tableDataList TableData
+
+					item := contractDetail.Service[i]
+					//表头备注信息
+					tableTitleSlice = append(tableTitleSlice, item.Title)
+					//表格数据
+					if item.HasDetail == "是" && len(item.DetailList) > 0 {
+						//表格每行数据切片
+						tableRowList := make([]TableRow, 0)
+
+						//遍历获取table行数据
+						for j := 0; j < len(item.DetailList); j++ {
+							//列数据样式初始化
+							isBold := false
+							backgrandColor := ""
+							fontSize := 10.0
+
+							//表头数据样式
+							if j == 0 {
+								isBold = true
+								backgrandColor = "gray_2"
+								fontSize = 12.0
+							}
+
+							//获取每一列的数据
+							tmpCellList, colErr := getColList(item.DetailList[j])
+							if colErr != nil {
+								err = colErr
+								return
+							}
+
+							//定义生成table列数据切片
+							tableCelList := make([]TableCel, 0)
+							lenCell := len(tmpCellList)
+							for k := 0; k < len(tmpCellList); k++ {
+								//计算出来每一列的宽度占比 start
+								//总宽度
+								newDecimal := decimal.NewFromFloat(100)
+								//总列数
+								newDecimal2 := decimal.NewFromInt(int64(lenCell))
+								//计算出来每一列的宽度占比(四舍五入)
+								widthPercent, _ := newDecimal.Div(newDecimal2).Round(3).Float64()
+								//if !ok {
+								//	err = errors.New("word普通数据表格宽度百分比计算失败")
+								//	return
+								//}
+								//计算出来每一列的宽度占比 end
+
+								tableCel := TableCel{
+									Value:     tmpCellList[k],
+									TextAlign: "center",
+									//ColumnSpan   int     `json:"column_span";description:"需要合同的列数量"`
+									//IsMerged     bool    `json:"is_merged";description:"是否需要上下行合并"`
+									Background:   backgrandColor,
+									IsBold:       isBold,
+									FontSize:     fontSize,
+									WidthPercent: widthPercent,
+								}
+								tableCelList = append(tableCelList, tableCel)
+							}
+
+							//将每行数据插入到table行数据切片之中
+							tableRow := TableRow{
+								RowList: tableCelList,
+							}
+							tableRowList = append(tableRowList, tableRow)
+						}
+						//赋值table表格数据
+						tableDataList.List = tableRowList
+					} else {
+						//获取预设的表格数据
+						contractServiceTemplate, tmpErr := contract_service_template.GetContractServiceTemplateById(item.ServiceTemplateId)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+
+						//赋值table表格数据
+						jsonStr := contractServiceTemplate.TableValue
+						err = json.Unmarshal([]byte(jsonStr), &tableDataList)
+						if err != nil {
+							return
+						}
+					}
+
+					//往word中添加表格数据
+					TableDataListSlice = append(TableDataListSlice, tableDataList)
+				}
+
+				//表格标题
+				titleStr := strings.Join(tableTitleSlice, "、")
+				title += titleStr + "的服务内容,详细如下:"
+
+				headerRun := headerPar.AddRun()
+				headerRun.AddBreak()
+				headerRunPro := headerRun.Properties()
+				headerRunPro.SetBold(text.IsBold)
+				headerRunPro.SetSize(measurement.Distance(fontSize * measurement.Point))
+				headerRunPro.SetFontFamily("宋体")
+				//headerRunPro.SetKerning(measurement.Distance(2 * fontSize * measurement.Point))
+				headerRun.AddText(title)
+
+				//生成表格
+				for _, tableDataList := range TableDataListSlice {
+					tmpErr := addTableV2(tableDataList, doc)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+				}
+			} else {
+				isPrint, printContent, tmpErr := getPrintData(text, contractDetail)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				if isPrint == false {
+					continue
+				}
+				if data.ElementType == "column" {
+					for j := 0; j < addTabNum; j++ {
+						//headerRun.AddTab()
+						printContent = "  " + printContent
+					}
+				}
+				fontSize := text.FontSize
+				if fontSize <= 0 {
+					fontSize = 15
+				}
+				headerRun2 := headerPar.AddRun()
+				headerRunPro2 := headerRun2.Properties()
+				headerRunPro2.SetBold(text.IsBold)
+				headerRunPro2.SetSize(measurement.Distance(fontSize * measurement.Point))
+				headerRunPro2.SetFontFamily("宋体")
+				//headerRunPro2.SetKerning(measurement.Distance(2 * fontSize * measurement.Point))
+				headerRun2.AddText(printContent)
+			}
+		}
+
+		for _, printMap := range printContentList {
+			//开始一个新的段落
+			headerPar := doc.AddParagraph()
+			headerParPro := headerPar.Properties()
+			headerParPro.Spacing().SetLineSpacing(measurement.Distance(1.5*fontSize*measurement.Point), wml.ST_LineSpacingRuleAuto)
+
+			//headerParPro.AddTabStop()
+			textAlign := getTextAlignConf(data.TextAlign)
+			headerParPro.SetAlignment(textAlign)
+			//空出三个字符出来,避免签名顶在最前方展示
+			headerParPro.SetStartIndent(measurement.Distance(3 * fontSize * measurement.Point))
+
+			headerRun := headerPar.AddRun()
+			headerRunPro := headerRun.Properties()
+			headerRunPro.SetBold(data.IsBold)
+			headerRunPro.SetSize(measurement.Distance(fontSize * measurement.Point))
+			headerRunPro.SetFontFamily("宋体")
+			//headerRunPro.SetKerning(measurement.Distance(2 * fontSize * measurement.Point))
+
+			headerRun.AddText(printMap[0])
+		}
+	}
+
+	//for _, data := range list {
+	//	headerPar := doc.AddParagraph()
+	//	headerParPro := headerPar.Properties()
+	//	headerParPro.SetAlignment(wml.ST_JcCenter)
+	//	headerRun := headerPar.AddRun()
+	//	headerRunPro := headerRun.Properties()
+	//	headerRunPro.SetBold(true)
+	//	headerRunPro.SetSize(16)
+	//	headerRun.AddText(data)
+	//}
+
+	err = doc.SaveToFile(wordPath)
+	return
+}
+
+// getPrintData 获取打印数据
+func getPrintData(data WordElement, contractDetail *contract.ContractDetail) (isPrint bool, printContent string, err error) {
+	printContent = data.Content
+	if data.RelationName != "" {
+		switch data.RelationName {
+		case "address":
+			if contractDetail.Address == "" && contractDetail.Province == "" && contractDetail.City == "" {
+				return
+			}
+		case "postcode":
+			if contractDetail.Postcode == "" {
+				return
+			}
+		case "phone":
+			if contractDetail.Phone == "" {
+				return
+			}
+		case "fax":
+			if contractDetail.Fax == "" {
+				return
+			}
+		case "remark":
+			if contractDetail.Remark == "" {
+				return
+			}
+		case "price":
+			//实际金额(小写)
+			if contractDetail.OriginalPrice == contractDetail.Price {
+				return
+			}
+		case "price_cn":
+			//实际金额(大写)
+			if contractDetail.OriginalPrice == contractDetail.Price {
+				return
+			}
+		case "pay_remark":
+			if contractDetail.PayRemark == "" {
+				return
+			}
+		case "company_name":
+			if contractDetail.CompanyName == "" {
+				return
+			}
+		}
+	}
+	switch data.ElementName {
+	case "address":
+		if contractDetail.Address == "" && contractDetail.Province == "" && contractDetail.City == "" {
+			return
+		}
+		printContent = getContractAddress(contractDetail)
+	case "postcode":
+		if contractDetail.Postcode == "" {
+			return
+		}
+		printContent = contractDetail.Postcode
+	case "phone":
+		if contractDetail.Phone == "" {
+			return
+		}
+		printContent = contractDetail.Phone
+	case "fax":
+		if contractDetail.Fax == "" {
+			return
+		}
+		printContent = contractDetail.Fax
+	case "remark":
+		if contractDetail.Remark == "" {
+			return
+		}
+		printContent = contractDetail.Remark
+	case "start_date":
+		printContent = contractDetail.StartDate.Format("2006年01月02日")
+	case "end_date":
+		printContent = contractDetail.EndDate.Format("2006年01月02日")
+	case "num_year":
+		////合同结束日期与合同开始日期的时间差(小时差)
+		//newDecimal := decimal.NewFromFloat(contractDetail.EndDate.Sub(contractDetail.StartDate).Hours())
+		////分母为365天 * 24 小时
+		//newDecimal2 := decimal.NewFromInt(24 * 365)
+		////计算出来相差多少年,保留一位小数(四舍五入)
+		//numYearDecimal := newDecimal.Div(newDecimal2).Round(1)
+		////定义最小年份差,不能小于0.1年
+		//minDecimal := decimal.NewFromFloat(0.1)
+		////如果计算出来的年份差小于0.1年,那么该年份差就赋值 0.1年
+		//if numYearDecimal.LessThan(minDecimal) {
+		//	numYearDecimal = minDecimal
+		//}
+		//printContent = numYearDecimal.String()
+		tmpPrintContent, tmpErr := CalculationDate(contractDetail.StartDate, contractDetail.EndDate)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		printContent = tmpPrintContent
+	case "original_price":
+		//优惠前金额(小写)
+		newDecimal := decimal.NewFromFloat(contractDetail.OriginalPrice)
+		printContent = newDecimal.String()
+	case "original_price_cn":
+		//优惠前金额(大写)
+		originalCnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.OriginalPrice)
+		if cnyErr != nil {
+			err = cnyErr
+			return
+		}
+		printContent = originalCnyPrice
+	case "price":
+		//实际金额(小写)
+		if contractDetail.OriginalPrice == contractDetail.Price {
+			return
+		}
+		newDecimal := decimal.NewFromFloat(contractDetail.Price)
+		printContent = newDecimal.String()
+	case "price_cn":
+		//实际金额(大写)
+		if contractDetail.OriginalPrice == contractDetail.Price {
+			return
+		}
+		cnyPrice, cnyErr := utils.ConvertNumToCny(contractDetail.Price)
+		if cnyErr != nil {
+			err = cnyErr
+			return
+		}
+		printContent = cnyPrice
+
+	case "pay_remark":
+		if contractDetail.PayRemark == "" {
+			return
+		}
+		printContent = contractDetail.PayRemark
+	case "company_name":
+		if contractDetail.CompanyName == "" {
+			return
+		}
+		printContent = contractDetail.CompanyName
+	case "company_name_sign":
+		if contractDetail.CompanyName == "" {
+			return
+		}
+		printContent = "甲方:" + contractDetail.CompanyName
+	}
+	isPrint = true
+	return
+}
+
+// addTableV2 添加表格数据
+func addTableV2(tableDataList TableData, doc *document.Document) (err error) {
+	//fmt.Println("表头名称:", title)
+	//插入一个新的段落
+	nowParagraph := doc.AddParagraph()
+	//表格数据
+
+	table := doc.InsertTableAfter(nowParagraph)
+	//设置表格宽度
+	tableWidth := 6.5
+	table.Properties().SetWidth(measurement.Distance(tableWidth * measurement.Inch))
+	//表格宽度设置为自动
+	//table.Properties().SetWidthAuto()
+
+	//边框
+	borders := table.Properties().Borders()
+	// thin borders
+	borders.SetAll(wml.ST_BorderSingle, color.Auto, measurement.Zero)
+
+	//表格数据
+	rowList := tableDataList.List
+	//每一列合并单元格状态map
+	rowIsMeged := make(map[int]bool)
+
+	//table.Properties().W
+	for i := 0; i < len(rowList); i++ {
+		//创建新的一行
+		row := table.AddRow()
+		//设置行高,第二个参数是设置固定值还是自动
+		row.Properties().SetHeight(30*measurement.Point, wml.ST_HeightRuleAtLeast)
+
+		//遍历列数据
+		rowDataList := rowList[i].RowList
+		if rowDataList != nil {
+			for j := 0; j < len(rowDataList); j++ {
+				//当前列是否合并
+				var isMeged bool
+				isMeged, ok := rowIsMeged[j]
+				if !ok {
+					rowIsMeged[j] = false
+					isMeged = false
+				}
+
+				cell := row.AddCell()
+				cellPara := cell.AddParagraph()
+				run := cellPara.AddRun()
+				//列数据
+				cellData := rowDataList[j]
+
+				//如果合并列大于0,那么就合并列
+				if cellData.ColumnSpan > 0 {
+					// column span / merged cells
+					cell.Properties().SetColumnSpan(cellData.ColumnSpan)
+					//_ = row.AddCell()
+				}
+
+				//如果指定了上下单元格合并,那么去合并上下单元格
+				if cellData.IsMerged {
+					//将当前合并单元格状态调整为true
+					rowIsMeged[j] = true
+					//合并单元格类型
+					var mergeVal wml.ST_Merge
+
+					if isMeged { //如果上一层已经是合并了,那么这一层是继续合并
+						mergeVal = wml.ST_MergeContinue
+					} else { //如果上一层不是合并,那么这一层是开始合并
+						mergeVal = wml.ST_MergeRestart
+					}
+					cell.Properties().SetVerticalMerge(mergeVal)
+				} else {
+					//将当前合并单元格状态调整为false,这样后续如果再次碰到合并单元格操作,就是重新开始合并了
+					rowIsMeged[j] = false
+				}
+
+				//背景色
+				if cellData.Background != "" {
+					cell.Properties().SetShading(wml.ST_ShdSolid, getColorConf(cellData.Background), color.Auto)
+				}
+
+				//填充内容(文字)垂直对齐方式
+				cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter)
+
+				//将单元格设置为宽度百分比
+				if cellData.WidthPercent > 0 {
+					//cell.Properties().SetWidthPercent(cellData.WidthPercent)
+					//因为libreOffice不支持百分比的设置表格宽度
+					cellWidth := tableWidth * cellData.WidthPercent * measurement.Inch / 100
+					cell.Properties().SetWidth(measurement.Distance(cellWidth))
+				}
+
+				//文字排版(居中、左、右)
+				if cellData.TextAlign != "" {
+					cellPara.Properties().SetAlignment(getTextAlignConf(cellData.TextAlign))
+					//cellPara.Properties().SetAlignment(wml.ST_JcLeft)
+				}
+
+				//cell.Properties().SetAli
+				//设置是否加粗
+				run.Properties().SetBold(cellData.IsBold)
+
+				//设置字体大小
+				fontSize := 10.0
+				if cellData.FontSize > 0 {
+					fontSize = cellData.FontSize
+				}
+				run.Properties().SetSize(measurement.Distance(fontSize * measurement.Point))
+
+				//设置段落间的间距
+				cellPara.Properties().Spacing().SetLineSpacing(measurement.Distance(1.4*fontSize*measurement.Point), wml.ST_LineSpacingRuleAuto)
+				//设置段前间距
+				cellPara.Properties().Spacing().SetBefore(measurement.Distance(0.9 * fontSize * measurement.Point))
+				//设置段后间距
+				cellPara.Properties().Spacing().SetAfter(measurement.Distance(0.5 * fontSize * measurement.Point))
+
+				//设置字体
+				run.Properties().SetFontFamily("宋体")
+
+				//设置排序
+				run.Properties().SetVerticalAlignment(sharedTypes.ST_VerticalAlignRunBaseline)
+
+				//设置显示的文字
+				if cellData.Value != "" {
+					strSlice := strings.Split(cellData.Value, "<br/>")
+					for s := 0; s < len(strSlice); s++ {
+						if s > 0 {
+							run.AddBreak()
+						}
+						run.AddText(strSlice[s])
+					}
+				} else {
+					run.AddText("")
+				}
+			}
+
+		}
+	}
+	return
+}
+
 // getContractAddress 获取展示的详细地址
 func getContractAddress(contractDetail *contract.ContractDetail) (address string) {
 	ignoreStrs := []string{"北京市", "上海市", "天津市", "重庆市"}
@@ -1066,3 +1796,143 @@ func getContractAddress(contractDetail *contract.ContractDetail) (address string
 	}
 	return
 }
+
+/**
+*@tips libreoffice 转换指令:
+* libreoffice6.2 invisible --convert-to pdf csDoc.doc --outdir /home/[转出目录]
+*
+* @function 实现文档类型转换为pdf或html
+* @param command:libreofficed的命令(具体以版本为准);win:soffice; linux:libreoffice6.2
+*     fileSrcPath:转换文件的路径
+*         fileOutDir:转换后文件存储目录
+*       converterType:转换的类型pdf/html
+* @return fileOutPath 转换成功生成的文件的路径 error 转换错误
+ */
+func FuncDocs2Pdf(command string, fileSrcPath string, fileOutDir string, converterType string) (fileOutPath string, error error) {
+	//校验fileSrcPath
+	srcFile, erByOpenSrcFile := os.Open(fileSrcPath)
+	if erByOpenSrcFile != nil && os.IsNotExist(erByOpenSrcFile) {
+		return "", erByOpenSrcFile
+	}
+	//如文件输出目录fileOutDir不存在则自动创建
+	outFileDir, erByOpenFileOutDir := os.Open(fileOutDir)
+	if erByOpenFileOutDir != nil && os.IsNotExist(erByOpenFileOutDir) {
+		erByCreateFileOutDir := os.MkdirAll(fileOutDir, os.ModePerm)
+		if erByCreateFileOutDir != nil {
+			fmt.Println("File ouput dir create error.....", erByCreateFileOutDir.Error())
+			return "", erByCreateFileOutDir
+		}
+	}
+	//关闭流
+	defer func() {
+		_ = srcFile.Close()
+		_ = outFileDir.Close()
+	}()
+	//convert
+	cmd := exec.Command(command, "--invisible", "--convert-to", converterType,
+		fileSrcPath, "--outdir", fileOutDir)
+	_, errByCmdStart := cmd.Output()
+	//命令调用转换失败
+	if errByCmdStart != nil {
+		return "", errByCmdStart
+	}
+	//success
+	fileOutPath = fileOutDir + "/" + strings.Split(path.Base(fileSrcPath), ".")[0]
+	if converterType == "html" {
+		fileOutPath += ".html"
+	} else {
+		fileOutPath += ".pdf"
+	}
+	//fmt.Println("文件转换成功...", string(byteByStat))
+	return fileOutPath, nil
+}
+
+// CalculationDate 计算两个日期之间相差n年m月y天
+func CalculationDate(startDate, endDate time.Time) (beetweenDay string, err error) {
+	//startDate := time.Date(2021, 3, 28, 0, 0, 0, 0, time.Now().Location())
+	//endDate := time.Date(2022, 3, 31, 0, 0, 0, 0, time.Now().Location())
+	numYear := endDate.Year() - startDate.Year()
+
+	numMonth := int(endDate.Month()) - int(startDate.Month())
+
+	numDay := 0
+	//获取截止月的总天数
+	endDateDays := getMonthDay(endDate.Year(), int(endDate.Month()))
+
+	//获取截止月的前一个月
+	endDatePrevMonthDate := endDate.AddDate(0, -1, 0)
+	//获取截止日期的上一个月的总天数
+	endDatePrevMonthDays := getMonthDay(endDatePrevMonthDate.Year(), int(endDatePrevMonthDate.Month()))
+	//获取开始日期的的月份总天数
+	startDateMonthDays := getMonthDay(startDate.Year(), int(startDate.Month()))
+
+	//判断,截止月是否完全被选中,如果相等,那么代表截止月份全部天数被选择
+	if endDate.Day() == endDateDays {
+		numDay = startDateMonthDays - startDate.Day() + 1
+
+		//如果剩余天数正好与开始日期的天数是一致的,那么月份加1
+		if numDay == startDateMonthDays {
+			numMonth++
+			numDay = 0
+			//超过月份了,那么年份加1
+			if numMonth == 12 {
+				numYear++
+				numMonth = 0
+			}
+		}
+	} else {
+		numDay = endDate.Day() - startDate.Day() + 1
+	}
+
+	//天数小于0,那么向月份借一位
+	if numDay < 0 {
+		//向上一个月借一个月的天数
+		numDay += endDatePrevMonthDays
+
+		//总月份减去一个月
+		numMonth = numMonth - 1
+	}
+
+	//月份小于0,那么向年份借一位
+	if numMonth < 0 {
+		//向上一个年借12个月
+		numMonth += 12
+
+		//总年份减去一年
+		numYear = numYear - 1
+	}
+	if numYear < 0 {
+		err = errors.New("日期异常")
+		return
+	}
+
+	if numYear > 0 {
+		beetweenDay += fmt.Sprint(numYear, "年")
+	}
+	if numMonth > 0 {
+		beetweenDay += fmt.Sprint(numMonth, "个月")
+	}
+	if numDay > 0 {
+		beetweenDay += fmt.Sprint(numDay, "天")
+	}
+	return
+}
+
+// getMonthDay 获取某年某月有多少天
+func getMonthDay(year, month int) (days int) {
+	if month != 2 {
+		if month == 4 || month == 6 || month == 9 || month == 11 {
+			days = 30
+
+		} else {
+			days = 31
+		}
+	} else {
+		if ((year%4) == 0 && (year%100) != 0) || (year%400) == 0 {
+			days = 29
+		} else {
+			days = 28
+		}
+	}
+	return
+}

+ 9 - 0
utils/config.go

@@ -34,6 +34,9 @@ var (
 	STATIC_DIR string
 )
 
+// LibreOfficePath LibreOfficePath的地址
+var LibreOfficePath string
+
 var (
 	Bucketname       string = "hongze"
 	Endpoint         string
@@ -93,6 +96,12 @@ func init() {
 		STATIC_DIR = "static/imgs/"
 		Endpoint = "oss-cn-shanghai.aliyuncs.com"
 	}
+
+	tmpLibreOfficePath, err := web.AppConfig.String("libreOfficePath")
+	if err != nil {
+		panic("配置文件读取libreOfficePath错误 " + err.Error())
+	}
+	LibreOfficePath = tmpLibreOfficePath
 }
 
 //http://webapi.brilliantstart.cn/api/