contractPdf.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. package order
  2. import (
  3. "app.yhyue.com/moapp/jybase/common"
  4. "app.yhyue.com/moapp/jybase/date"
  5. "context"
  6. "crypto/md5"
  7. "encoding/hex"
  8. "fmt"
  9. "github.com/gogf/gf/v2/errors/gerror"
  10. "github.com/gogf/gf/v2/frame/g"
  11. "github.com/gogf/gf/v2/util/gconv"
  12. "github.com/lukasjarosch/go-docx"
  13. "github.com/pkg/errors"
  14. cncap "github.com/rosbit/cn-capitalizer"
  15. "jyOrderManager/internal/jyutil"
  16. "jyOrderManager/internal/model"
  17. "jyOrderManager/internal/service"
  18. "log"
  19. "math/rand"
  20. "os/exec"
  21. "path/filepath"
  22. "runtime"
  23. "strconv"
  24. "strings"
  25. "time"
  26. )
  27. const (
  28. bigMemberContractNoFlag = "JYDZ05"
  29. subVipContractNoFlag = "JYDZ06"
  30. staticSourceDir = "resource/contract/"
  31. /*
  32. 1 超级订阅:https://www.kdocs.cn/l/cpqTn293crus
  33. 2 大会员(购买主体“企业”,非单省版):https://www.kdocs.cn/l/cv9HKtmoO3tj
  34. 3 大会员(购买主体“企业”,单省版):https://www.kdocs.cn/l/cm6OWJbTgmhE
  35. 4 大会员(购买主体“个人”,非单省版-有子账号):https://www.kdocs.cn/l/cgtyiJGraYmd
  36. 5 大会员(购买主体“个人”,非单省版-无子账号):https://www.kdocs.cn/l/clxt7vdFZ5Ea
  37. 6 大会员(购买主体“个人”,单省版-无子账号):https://www.kdocs.cn/l/cscOCLIvvG1a;
  38. 7 大会员(购买主体“个人”,单省版-有子账号):https://www.kdocs.cn/l/cfPHDdQsROBh
  39. */
  40. ContractTemplate_SUBVIP = "剑鱼标讯超级订阅产品服务协议书"
  41. ContractTemplate_MEMBER_ENT_NOTSINGLE = "剑鱼标讯大会员产品服务协议书-(企业-非单省版)"
  42. ContractTemplate_MEMBER_ENT_SINGLE = "剑鱼标讯大会员产品服务协议书-(企业-单省版)"
  43. ContractTemplate_MEMBER_PERSON_NOTSINGLE_ACCOUNT = "剑鱼标讯大会员产品服务协议书-(个人-非单省版-有子账号)"
  44. ContractTemplate_MEMBER_PERSON_NOTSINGLE_NOTACCOUNT = "剑鱼标讯大会员产品服务协议书-(个人-非单省版-无子账号)"
  45. ContractTemplate_MEMBER_PERSON_SINGLE_NOTACCOUNT = "剑鱼标讯大会员产品服务协议书-(个人-单省版-无子账号)"
  46. ContractTemplate_MEMBER_PERSON_SINGLE_ACCOUNT = "剑鱼标讯大会员产品服务协议书-(个人-单省版-有子账号)"
  47. )
  48. func GetContractPdf(ctx context.Context, orderCode, userName string, onlyQuery bool) (interface{}, error) {
  49. //detail, orderQueryErr := func() (map[string]interface{}, error) {
  50. // if num, _ := g.DB().GetCount(ctx, "SELECT * FROM jy_order_detail WHERE order_code=?", orderCode); num != 1 {
  51. // return nil, fmt.Errorf("当前订单不支持合同下载")
  52. // }
  53. // orderRes, err := g.DB().GetOne(ctx, "SELECT c.filter,c.service_type,a.audit_status,c.product_type,a.vip_type,a.salesperson,a.buy_subject,a.signing_subject,b.contract_status,b.seal_type,b.partyA_type,b.partyA_name,b.partyA_person,b.partyA_tel,b.partyA_address,b.partyB_person,b.remark,b.contract_money FROM dataexport_order a INNER JOIN jy_order_detail c ON a.order_code=c.order_code INNER JOIN contract b ON a.order_code=b.order_code WHERE a.order_code=? ", orderCode)
  54. // if err != nil {
  55. // return nil, err
  56. // }
  57. //
  58. // if orderRes.IsEmpty() {
  59. // return nil, fmt.Errorf("未找到订单")
  60. // }
  61. // return orderRes.Map(), nil
  62. //}()
  63. //
  64. //if orderQueryErr != nil {
  65. // return nil, gerror.Wrapf(orderQueryErr, "GetContractPdf orderQueryErr %s订单查询异常 %v\n", orderCode, orderQueryErr)
  66. //}
  67. var (
  68. errMsgArr []string
  69. filterMap map[string]interface{}
  70. detail map[string]interface{}
  71. )
  72. _ = func() map[string]interface{} {
  73. orderRes, err := g.DB().GetOne(ctx, "SELECT c.filter,c.service_type,c.product_code,a.audit_status,c.product_type,a.vip_type,a.salesperson,a.buy_subject,a.signing_subject,b.contract_status,b.seal_type,b.partyA_type,b.partyA_name,b.partyA_person,b.partyA_tel,b.partyA_address,b.partyB_person,b.remark,b.contract_money,b.contract_time FROM dataexport_order a INNER JOIN jy_order_detail c ON a.order_code=c.order_code INNER JOIN contract b ON a.order_code=b.order_code WHERE a.order_code=? and c.status =1", orderCode)
  74. if err != nil || orderRes.IsEmpty() {
  75. errMsgArr = append(errMsgArr, "未知订单")
  76. return nil
  77. }
  78. detail = orderRes.Map()
  79. filterMap = gconv.Map(detail["filter"])
  80. /*
  81. 判断是否符合合同
  82. (1)订单审核状态是“已通过”;
  83. (2)签约主体为“北京剑鱼信息技术有限公司”,注:如签约主体为拓普则线下生成合同;
  84. (3)协议状态为“签协议”(“已签协议”文案修改为“签协议”,涉及:创建订单、订单审核、订单详情)
  85. (4)产品类型是“超级订阅”(且付费类型为“购买”、“续费”),或产品类型是“大会员”且会员套餐为“商机版2.0”、“专家版2.0”(且服务类型为“新购服务”、“延长服务”),注:超级订阅/大会员升级和其他产品类型,线下生成合同。
  86. */
  87. if gconv.Int64(detail["audit_status"]) != 3 {
  88. errMsgArr = append(errMsgArr, "订单审核状态不是“已通过”")
  89. }
  90. if common.ObjToString(detail["signing_subject"]) != "h01" {
  91. errMsgArr = append(errMsgArr, "“签约主体”不是北京剑鱼信息技术有限公司")
  92. }
  93. if gconv.Int64(detail["contract_status"]) != 1 {
  94. errMsgArr = append(errMsgArr, "“协议状态”不是“签协议”")
  95. }
  96. var service_type = gconv.Int(detail["service_type"])
  97. switch common.ObjToString(detail["product_type"]) {
  98. case "VIP订阅":
  99. if !(service_type == 1 || service_type == 2) {
  100. errMsgArr = append(errMsgArr, "产品不符合条件")
  101. return nil
  102. }
  103. case "大会员":
  104. level := gconv.Int64(filterMap["comboId"])
  105. if !((service_type == 1 || service_type == 2) && (level == 6 || level == 7 || level == 30190)) {
  106. errMsgArr = append(errMsgArr, "产品不符合条件")
  107. return nil
  108. }
  109. default:
  110. errMsgArr = append(errMsgArr, "当前订单类型不支持生成电子合同")
  111. }
  112. orderDetailRes, err := g.DB().Query(ctx, "SELECT tactics FROM jy_order_detail WHERE order_code=? and status =1", orderCode)
  113. if err == nil {
  114. if orderDetailRes.Size() > 1 {
  115. errMsgArr = append(errMsgArr, "产品不符合条件")
  116. } else if orderDetailRes.Size() == 1 {
  117. if gconv.Int(orderDetailRes.List()[0]["tactics"]) != 1 {
  118. errMsgArr = append(errMsgArr, "非购买的商品不支持生成电子合同")
  119. }
  120. }
  121. }
  122. return nil
  123. }()
  124. if len(errMsgArr) > 0 {
  125. log.Printf("GetContractPdf contractPass %s合同校验异常 %v\n", orderCode, errMsgArr)
  126. return nil, fmt.Errorf(strings.Join(errMsgArr, ","))
  127. }
  128. if onlyQuery {
  129. return nil, nil
  130. }
  131. //
  132. contractDetail, err := func() (*ContractDetail, error) {
  133. remark := common.ObjToString(detail["remark"])
  134. ctd := &ContractDetail{
  135. ContractFromInfo: &ContractFromInfo{
  136. SealType: gconv.Int(detail["seal_type"]),
  137. PartyAType: gconv.Int(detail["partyA_type"]),
  138. PartyAName: common.ObjToString(detail["partyA_name"]),
  139. PartyAPerson: common.ObjToString(detail["partyA_person"]),
  140. PartyAContact: common.ObjToString(detail["partyA_tel"]),
  141. PartyAAddress: common.ObjToString(detail["partyA_address"]),
  142. PartyBPerson: common.ObjToString(detail["partyB_person"]),
  143. Remark: common.ObjToString(common.If(remark != "", remark, "无")),
  144. ContractTime: gconv.String(detail["contract_time"]),
  145. },
  146. Amount: common.Float64All(detail["contract_money"]),
  147. }
  148. buy_subject := gconv.Int64(detail["buy_subject"])
  149. var filter model.VipCycleFilter
  150. if err := gconv.Struct(filterMap, &filter); err != nil {
  151. return nil, gerror.Wrapf(err, "格式化超级订阅内容异常")
  152. }
  153. //时间
  154. switch filter.BuyType { //1天 2月 3年 4季度
  155. case 1:
  156. ctd.ServiceTime = fmt.Sprintf("%d天(自然日)", filter.BuyCycle)
  157. case 2:
  158. ctd.ServiceTime = fmt.Sprintf("%d个月", filter.BuyCycle)
  159. case 3:
  160. ctd.ServiceTime = fmt.Sprintf("%d个月", filter.BuyCycle*12)
  161. case 4:
  162. ctd.ServiceTime = fmt.Sprintf("%d个月", filter.BuyCycle*3)
  163. default:
  164. return nil, fmt.Errorf("未知周期单位")
  165. }
  166. if filter.GiftCycle > 0 {
  167. switch filter.GiftType { //1天 2月 3年 4季度
  168. case 1:
  169. ctd.ServiceTime += fmt.Sprintf("送%d天(自然日)", filter.GiftCycle)
  170. case 2:
  171. ctd.ServiceTime += fmt.Sprintf("送%d个月", filter.GiftCycle)
  172. case 3:
  173. ctd.ServiceTime += fmt.Sprintf("送%d个月", filter.GiftCycle*12)
  174. case 4:
  175. ctd.ServiceTime += fmt.Sprintf("送%d个月", filter.GiftCycle*3)
  176. }
  177. }
  178. ctd.AccountNum = 1 + filter.BuyAccountCount + filter.GiftAccountCount
  179. switch common.ObjToString(detail["product_type"]) {
  180. case "VIP订阅":
  181. p, err := service.JyProductManager.GetProduct(gconv.String(detail["product_code"]))
  182. if err != nil {
  183. return nil, err
  184. }
  185. if p.UnitNum > 0 {
  186. ctd.Service = fmt.Sprintf("省份版超级订阅(%d个省)", p.UnitNum)
  187. } else {
  188. ctd.Service = "全国版超级订阅"
  189. }
  190. ctd.ContractFlag = subVipContractNoFlag
  191. ctd.ContractTemplate = ContractTemplate_SUBVIP
  192. case "大会员":
  193. level := gconv.Int64(filterMap["comboId"])
  194. isSINGLE := gconv.Int64(filterMap["areaCount"]) == 1 //是单省版
  195. //hasACCOUNT := gconv.Int64(filterMap["free_sub_num"])+gconv.Int64(filterMap["pay_sub_num"]) >= 1 //有子账号
  196. //if buy_subject == 2 {
  197. // ctd.AccountNum = gconv.Int64(detail["buy_count"])
  198. //}
  199. if level == 6 {
  200. ctd.Service = "大会员商机版2.0"
  201. } else if level == 30190 {
  202. ctd.Service = "大会员商机版2.0(单省版)"
  203. } else if level == 7 {
  204. ctd.Service = "大会员专家版2.0"
  205. }
  206. //cycleType := gconv.Int64(filterMap["cycleType"])
  207. //if cycleType == 1 {
  208. // ctd.ServiceTime = fmt.Sprintf("%d天(自然日)", gconv.Int64(filterMap["cycle"]))
  209. //} else {
  210. // ctd.ServiceTime = fmt.Sprintf("%d个月", gconv.Int64(filterMap["cycle"]))
  211. //}
  212. if buy_subject == 1 { //个人
  213. if isSINGLE {
  214. if ctd.AccountNum > 1 {
  215. ctd.ContractTemplate = ContractTemplate_MEMBER_PERSON_SINGLE_ACCOUNT
  216. } else {
  217. ctd.ContractTemplate = ContractTemplate_MEMBER_PERSON_SINGLE_NOTACCOUNT
  218. }
  219. } else {
  220. if ctd.AccountNum > 1 {
  221. ctd.ContractTemplate = ContractTemplate_MEMBER_PERSON_NOTSINGLE_ACCOUNT
  222. } else {
  223. ctd.ContractTemplate = ContractTemplate_MEMBER_PERSON_NOTSINGLE_NOTACCOUNT
  224. }
  225. }
  226. } else { //企业
  227. if isSINGLE {
  228. ctd.ContractTemplate = ContractTemplate_MEMBER_ENT_SINGLE
  229. } else {
  230. ctd.ContractTemplate = ContractTemplate_MEMBER_ENT_NOTSINGLE
  231. }
  232. }
  233. ctd.ContractFlag = bigMemberContractNoFlag
  234. }
  235. return ctd, nil
  236. }()
  237. if err != nil {
  238. return nil, gerror.Wrapf(err, "获取合同内容出错")
  239. }
  240. filePath, getFilePathErr := func() (string, error) {
  241. md5Str := contractDetail.GetMd5()
  242. contract, err := g.DB().GetOne(ctx, "SELECT file_path FROM contract_pdf WHERE order_code=? and md5=?", orderCode, md5Str)
  243. if err != nil {
  244. return "", gerror.Wrapf(err, "获取pdf电子合同异常")
  245. }
  246. if contract.IsEmpty() {
  247. now := time.Now()
  248. docxAbsolutePath, err := contractDetail.getDocxFile(now)
  249. if err != nil {
  250. return "", errors.Wrap(err, "生成电子合同docx文件异常")
  251. }
  252. if err = contractDetail.convertWordToPDF(docxAbsolutePath); err != nil {
  253. return "", errors.Wrap(err, "生成电子合同pdf文件异常")
  254. }
  255. // 获取访问路径
  256. var (
  257. pdfFilePath = jyutil.GetRequestPath(strings.Replace(docxAbsolutePath, ".docx", ".pdf", 1))
  258. contractCode = contractDetail.GetContractNoStr(now)
  259. )
  260. g.Log().Infof(ctx, "xxx %s", contractCode)
  261. if _, err := g.DB().Save(ctx, "contract_pdf", g.Map{
  262. "order_code": orderCode,
  263. "code": contractCode,
  264. "md5": md5Str,
  265. "create_person": userName,
  266. "file_path": pdfFilePath,
  267. "create_time": now.Format(date.Date_Full_Layout),
  268. }); err != nil {
  269. return "", errors.Wrapf(err, "保存pdf内容异常")
  270. }
  271. if _, err := g.DB().Update(ctx, "contract", g.Map{
  272. "contract_code": contractCode,
  273. }, "order_code=?", orderCode); err != nil {
  274. return "", errors.Wrapf(err, "更新合同异常")
  275. }
  276. return pdfFilePath, nil
  277. }
  278. return gconv.String(contract.Map()["file_path"]), nil
  279. }()
  280. if getFilePathErr != nil {
  281. log.Printf("GetContractPdf getFilePathErr %s合同生成异常 %v\n", orderCode, getFilePathErr)
  282. return nil, getFilePathErr
  283. }
  284. return filePath, nil
  285. }
  286. type ContractFromInfo struct {
  287. SealType int `json:"seal_type" doc:"协议类型 1:有电子章 2:无电子章"`
  288. PartyAType int `json:"partyAType" doc:"甲方类型 1:个人,2:企业"`
  289. PartyAName string `json:"partyAName" doc:"甲方名称"`
  290. PartyAPerson string `json:"partyAPerson" doc:"甲方联系人"`
  291. PartyAContact string `json:"partyAContact" doc:"甲方联系方式"`
  292. PartyAAddress string `json:"partyAAddresst" doc:"甲方联系地址"`
  293. PartyBPerson string `json:"partyBPerson" doc:"乙方联系人"`
  294. Remark string `json:"remark" doc:"协议备注"`
  295. ContractTime string `json:"contractTime" doc:"协议签订时间"`
  296. }
  297. type ContractDetail struct {
  298. *ContractFromInfo
  299. Service string `json:"service" doc:"服务内容"`
  300. AccountNum int `json:"accountNum" doc:"账户个数"`
  301. ServiceTime string `json:"serviceTime" doc:"时长"`
  302. Amount float64 `json:"amount" doc:"金额"`
  303. ContractNo string `json:"contractNo" doc:"协议编号"`
  304. ContractFlag string `json:"contractFlag" doc:"协议编号标识"`
  305. ContractTemplate string `json:"contractTemplate" doc:"模版名字"`
  306. }
  307. // GetMd5 计算合同md5 (甲方+甲方类型+联系人+联系方式+地址+备注+乙方联系人+是否有电子章+服务内容+金额+账户个数+时长)
  308. func (detail *ContractDetail) GetMd5() string {
  309. hasher := md5.New()
  310. hasher.Write([]byte(fmt.Sprintf("%s_%d_%s_%s_%s_%s_%s_%d_%s_%d_%d_%s", detail.PartyAName, detail.PartyAType, detail.PartyAPerson, detail.PartyAContact,
  311. detail.PartyAAddress, detail.Remark, detail.PartyBPerson, detail.SealType, detail.Service, detail.Amount, detail.AccountNum, detail.ServiceTime)))
  312. hashInBytes := hasher.Sum(nil)
  313. return hex.EncodeToString(hashInBytes)
  314. }
  315. func (detail *ContractDetail) getDocxFile(createTime time.Time) (filePath string, err error) {
  316. replaceMap := docx.PlaceholderMap{
  317. "PartyA.Name": detail.PartyAName, //甲方名字 {PartyA.Name}
  318. "PartyA.Person": detail.PartyAPerson, //甲方联系人 {PartyA.Person}
  319. "PartyA.Tel": detail.PartyAContact, //甲方联系方式 {PartyA.Tel}
  320. "PartyA.Addr": detail.PartyAAddress, //甲方地址 {PartyA.Addr}
  321. "Contract.No": detail.GetContractNoStr(createTime), //合同编号 {Contract.No}
  322. "Contract.Service.Detail": detail.Service, //会员服务 {Contract.Service.Detail}
  323. "Contract.Service.Time": detail.ServiceTime, //服务时长 {Contract.Service.Time}
  324. "Contract.Account.Num": detail.AccountNum, //账号个数 {Contract.Account.Num}
  325. "Contract.Remark": detail.Remark, //合同备注 {Contract.Remark}
  326. "Contract.Amount": strconv.FormatFloat(detail.Amount/100, 'f', -1, 64), //合同金额 {Contract.Amount}
  327. "Contract.Amount.Font": cncap.CapitalizeCurrency(detail.Amount / 100.0), //合同大写金额 {Contract.Amount.Font}
  328. "PartyB.Person": detail.PartyBPerson, //乙方联系人 {PartyB.Person}
  329. "PartyB.Date.Y": createTime.Year(), //乙方日期-年 {PartyB.Date.Year}
  330. "PartyB.Date.M": fmt.Sprintf("%02d", int(createTime.Month())), //乙方日期-月 {PartyB.Date.Month}
  331. "PartyB.Date.D": fmt.Sprintf("%02d", createTime.Day()), //乙方日期-日 {PartyB.Date.Day}
  332. "PartyA.Date.Y": "", //甲方日期-年 {PartyA.Date.Year}
  333. "PartyA.Date.M": "", //甲方日期-月 {PartyA.Date.Month}
  334. "PartyA.Date.D": "", //甲方日期-日 {PartyA.Date.Day}
  335. }
  336. if detail.ContractTime != "" {
  337. if t, err := time.ParseInLocation(time.DateTime, detail.ContractTime, time.Local); err == nil {
  338. replaceMap["PartyB.Date.Y"] = t.Year() //乙方日期-年 {PartyB.Date.Year}
  339. replaceMap["PartyB.Date.M"] = fmt.Sprintf("%02d", int(t.Month())) //乙方日期-月 {PartyB.Date.Month}
  340. replaceMap["PartyB.Date.D"] = fmt.Sprintf("%02d", t.Day()) //乙方日期-日 {PartyB.Date.Day}
  341. replaceMap["PartyA.Date.Y"] = t.Year() //甲方日期-年 {PartyA.Date.Year}
  342. replaceMap["PartyA.Date.M"] = fmt.Sprintf("%02d", int(t.Month())) //甲方日期-月 {PartyA.Date.Month}
  343. replaceMap["PartyA.Date.D"] = fmt.Sprintf("%02d", t.Day()) //甲方日期-日 {PartyA.Date.Day}
  344. }
  345. }
  346. if detail.PartyAType == 2 {
  347. replaceMap["PartyA.EndName"] = detail.PartyAName
  348. } else {
  349. replaceMap["PartyA.EndName"] = ""
  350. }
  351. numPageEmptyRows := 0
  352. longNum := len([]rune(fmt.Sprintf("%s%s%d%s", detail.Service, detail.ServiceTime, detail.AccountNum, detail.Remark)))
  353. if longNum <= 30 { //备注过短时,公章上移 18+13
  354. numPageEmptyRows = 3
  355. } else if longNum > 30 && longNum <= 88 {
  356. numPageEmptyRows = 2
  357. } else if longNum > 88 && longNum <= 146 {
  358. numPageEmptyRows = 1
  359. }
  360. replaceMap["PageEmptyRows"] = strings.Repeat(" ", numPageEmptyRows*180)
  361. //计算第一页空行数,把落款固定到最下部
  362. // read and parse the template docx
  363. doc, err := docx.Open(fmt.Sprintf("%s%s.docx", staticSourceDir, detail.ContractTemplate))
  364. if err != nil {
  365. return "", errors.Wrap(err, "docx文件模版文件异常")
  366. }
  367. // replace the keys with values from replaceMap
  368. err = doc.ReplaceAll(replaceMap)
  369. if err != nil {
  370. return "", errors.Wrap(err, "docx文件模版文件参数替换异常")
  371. }
  372. fileAbsolutePath := jyutil.GetFilePath("docx", "contract")
  373. // write out a new file
  374. err = doc.WriteToFile(fileAbsolutePath)
  375. if err != nil {
  376. return "", errors.Wrap(err, "docx文件生成异常")
  377. }
  378. return fileAbsolutePath, nil
  379. }
  380. // GetContractNoStr 获取生成协议编号
  381. func (detail *ContractDetail) GetContractNoStr(createTime time.Time) string {
  382. if detail.ContractNo != "" {
  383. return detail.ContractNo
  384. }
  385. dataStr := createTime.Format(date.Date_yyyyMMdd)
  386. num, _ := g.Redis("newother").Incr(context.Background(), fmt.Sprintf("%s_%s", detail.ContractFlag, dataStr))
  387. detail.ContractNo = fmt.Sprintf("%s【%s】第%03d号", detail.ContractFlag, dataStr, num)
  388. return detail.ContractNo
  389. }
  390. // convertWordToPDF word转pdf
  391. func (detail *ContractDetail) convertWordToPDF(inputDocxPath string) error {
  392. var commandName string
  393. var commandArg string
  394. switch runtime.GOOS {
  395. case "windows":
  396. commandName = "cmd"
  397. commandArg = "/c"
  398. case "linux", "darwin":
  399. commandName = "sh"
  400. commandArg = "-c"
  401. default:
  402. return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
  403. }
  404. fileDir := filepath.Dir(inputDocxPath)
  405. fileFullName := filepath.Base(inputDocxPath)
  406. fileName := strings.TrimSuffix(fileFullName, filepath.Ext(fileFullName))
  407. pdfFileFullPath := fmt.Sprintf("%s/%s.pdf", fileDir, fileName) //生成pdf路径
  408. pngFileFullPath := fmt.Sprintf("%s/%s.png", fileDir, fileName) //生成图片路径
  409. pngsMatchPath := fmt.Sprintf("%s/%s-*.png", fileDir, fileName) //生成图片路径
  410. // 一、soffice 生成pdf文件
  411. createPdfFile := exec.Command(commandName, commandArg, fmt.Sprintf("soffice --headless --invisible --convert-to pdf %s --outdir %s", inputDocxPath, fileDir))
  412. if _, err := createPdfFile.CombinedOutput(); err != nil {
  413. fmt.Printf(fmt.Sprintf("soffice --headless --invisible --convert-to pdf %s --outdir %s", inputDocxPath, fileDir))
  414. return errors.Wrap(err, "Command execution createPdfFile failed:")
  415. }
  416. // 二、convert pdf内容转为图片
  417. var hasSeal bool = detail.SealType == 1
  418. if hasSeal {
  419. createPngFile := exec.Command(commandName, commandArg, fmt.Sprintf("convert -density 300x300 -units pixelsperinch %s -background white -alpha remove -alpha off -quality 100 -antialias -resize 100%% %s ", pdfFileFullPath, pngFileFullPath))
  420. if _, err := createPngFile.CombinedOutput(); err != nil {
  421. fmt.Printf("convert -density 300x300 -units pixelsperinch %s -background white -alpha remove -alpha off -quality 100 -antialias -resize 100%% %s ", pdfFileFullPath, pngFileFullPath)
  422. return errors.Wrap(err, "Command execution createPngFile failed:")
  423. }
  424. //每张图片
  425. cmd := exec.Command("sh", "-c", fmt.Sprintf("ls %s", pngsMatchPath))
  426. out, err := cmd.CombinedOutput()
  427. if err != nil || len(out) == 0 {
  428. return errors.Wrap(err, "Command execution ls pngs failed:")
  429. }
  430. for index, fileName := range strings.Split(string(out), "\n") {
  431. if fileTmpPath := strings.TrimSpace(fileName); fileTmpPath != "" {
  432. var x, y int64
  433. rand.Seed(time.Now().UnixNano())
  434. // 随机盖章位置 生成一个随机整数
  435. if index == 0 { //首页需要盖着日期
  436. x = 680 + rand.Int63n(10)
  437. y = 130 + rand.Int63n(30)
  438. //if len([]rune(fmt.Sprintf("%s%s%d%s", detail.Service, detail.ServiceTime, detail.AccountNum, detail.Remark))) <= 32 { //备注过短时,公章上移
  439. // y = y + 50
  440. //}
  441. } else {
  442. x = 300 + rand.Int63n(200)
  443. y = 500 + rand.Int63n(200)
  444. }
  445. exeSeal := exec.Command(commandName, commandArg, fmt.Sprintf("convert %s \\( %s -resize 50%% \\) -gravity southeast -geometry +%d+%d -composite %s", fileTmpPath, fmt.Sprintf("%sz.png", staticSourceDir), x, y, fileTmpPath))
  446. if _, err := exeSeal.CombinedOutput(); err != nil {
  447. return errors.Wrap(err, "Command execution exeSeal failed:")
  448. }
  449. }
  450. }
  451. createPngPdfFile := exec.Command(commandName, commandArg, fmt.Sprintf("convert %s %s", pngsMatchPath, pdfFileFullPath))
  452. if _, err := createPngPdfFile.CombinedOutput(); err != nil {
  453. fmt.Printf("convert %s %s", pngsMatchPath, pdfFileFullPath)
  454. return errors.Wrap(err, "Command execution createPngFile failed:")
  455. }
  456. } else {
  457. createPngPdfFile := exec.Command(commandName, commandArg, fmt.Sprintf("convert -density 300x300 -units pixelsperinch %s -background white -alpha remove -alpha off -quality 100 -antialias -resize 100%% %s && convert %s %s", pdfFileFullPath, pngFileFullPath, pngsMatchPath, pdfFileFullPath))
  458. if _, err := createPngPdfFile.CombinedOutput(); err != nil {
  459. fmt.Println(fmt.Sprintf("convert -density 300x300 -units pixelsperinch %s -background white -alpha remove -alpha off -quality 100 -antialias -resize 100%% %s && convert %s %s", pdfFileFullPath, pngFileFullPath, pngsMatchPath, pdfFileFullPath))
  460. return errors.Wrap(err, "Command execution createPngFile failed:")
  461. }
  462. }
  463. // 三、清除多余文件
  464. clearFileCmd := exec.Command(commandName, commandArg, fmt.Sprintf("chmod -x %s && rm -rf %s %s", pngsMatchPath, pngsMatchPath, inputDocxPath))
  465. if err := clearFileCmd.Run(); err != nil {
  466. return errors.Wrap(err, "Command execution clearFileCmd failed:")
  467. }
  468. return nil
  469. }