Files
tyapi-server/internal/shared/pdf/qygl_report_pdf.go

1812 lines
52 KiB
Go
Raw Normal View History

2026-03-11 17:23:09 +08:00
package pdf
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/jung-kurt/gofpdf/v2"
"go.uber.org/zap"
)
// GenerateQYGLReportPDF 使用 gofpdf 根据 QYGLJ1U9 聚合结果生成企业全景报告 PDF
// 尽量复刻 qiye.html 中的板块顺序和展示逻辑。
// report 参数通常是从 JSON 反序列化得到的 map[string]interface{}。
func GenerateQYGLReportPDF(_ context.Context, logger *zap.Logger, report map[string]interface{}) ([]byte, error) {
if logger == nil {
logger = zap.NewNop()
}
pdf := gofpdf.New("P", "mm", "A4", "")
// 整体页边距和自动分页底部预留稍微加大,整体更「松」一点
pdf.SetMargins(15, 25, 15)
pdf.SetAutoPageBreak(true, 22)
// 加载中文字体
fontManager := NewFontManager(logger)
chineseFontLoaded := fontManager.LoadChineseFont(pdf)
fontName := "ChineseFont"
if !chineseFontLoaded {
// 回退:使用内置核心字体(英文),中文可能会显示为方块
fontName = "Arial"
}
// 整体默认正文字体稍微放大
pdf.SetFont(fontName, "", 14)
// 头部信息,对齐 qiye.html
entName := getString(report, "entName")
if entName == "" {
entName = "未知企业"
}
creditCode := getString(report, "creditCode")
if creditCode == "" {
creditCode = getStringFromMap(report, "basic", "creditCode")
}
basic := getMap(report, "basic")
entStatus := getString(basic, "status")
if entStatus == "" {
entStatus = getString(basic, "entStatus")
}
entType := getString(basic, "entType")
reportTime := getString(report, "reportTime")
riskOverview := getMap(report, "riskOverview")
riskScore := getString(riskOverview, "riskScore")
riskLevel := getString(riskOverview, "riskLevel")
// 首页头部
pdf.AddPage()
// 预留更多顶部空白,让标题整体再下移一些
pdf.Ln(20)
// 顶部大标题:居中、加粗、蓝色,更大字号
pdf.SetFont(fontName, "B", 34)
pdf.SetTextColor(37, 99, 235)
pdf.CellFormat(0, 18, "企业全景报告", "", 1, "C", false, 0, "")
pdf.Ln(10)
// 恢复正文颜色
pdf.SetTextColor(0, 0, 0)
pdf.SetFont(fontName, "", 18)
pdf.MultiCell(0, 9, fmt.Sprintf("企业名称:%s", entName), "", "L", false)
pdf.SetFont(fontName, "", 14)
pdf.MultiCell(0, 7, fmt.Sprintf("统一社会信用代码:%s", safePlaceholder(creditCode)), "", "L", false)
pdf.MultiCell(0, 7, fmt.Sprintf("经营状态:%s", safePlaceholder(entStatus)), "", "L", false)
pdf.MultiCell(0, 7, fmt.Sprintf("企业类型:%s", safePlaceholder(entType)), "", "L", false)
if reportTime != "" {
pdf.MultiCell(0, 7, fmt.Sprintf("报告生成时间:%s", reportTime), "", "L", false)
}
// 综合风险评分(使用 riskOverview
pdf.Ln(6)
pdfSubTitle(pdf, fontName, "综合风险评分")
pdf.SetFont(fontName, "", 13)
if riskScore != "" {
pdf.MultiCell(0, 7, fmt.Sprintf("风险得分:%s", riskScore), "", "L", false)
}
if riskLevel != "" {
pdf.MultiCell(0, 7, fmt.Sprintf("风险等级:%s", riskLevel), "", "L", false)
}
// 头部风险标签
if tags, ok := riskOverview["tags"].([]interface{}); ok && len(tags) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "", 11)
pdf.MultiCell(0, 5, "风险标签:", "", "L", false)
for _, t := range tags {
pdf.MultiCell(0, 5, fmt.Sprintf("· %v", t), "", "L", false)
}
}
// 对齐 qiye.html 的板块顺序
sections := []struct {
key string
title string
}{
{"riskOverview", "风险情况(综合分析)"},
{"basic", "一、主体概览(企业基础信息)"},
{"branches", "二、分支机构"},
{"shareholding", "三、股权与控制"},
{"controller", "四、实际控制人"},
{"beneficiaries", "五、最终受益人"},
{"investments", "六、对外投资"},
{"guarantees", "七、对外担保"},
{"management", "八、人员与组织"},
{"assets", "九、资产与经营(年报)"},
{"licenses", "十、行政许可与资质"},
{"activities", "十一、经营活动"},
{"inspections", "十二、抽查检查"},
{"risks", "十三、风险与合规"},
{"timeline", "十四、发展时间线"},
{"listed", "十五、上市信息"},
}
for _, s := range sections {
switch s.key {
case "riskOverview":
renderPDFRiskOverview(pdf, fontName, s.title, riskOverview)
case "basic":
renderPDFBasic(pdf, fontName, s.title, basic, entName, creditCode)
case "branches":
renderPDFBranches(pdf, fontName, s.title, getSlice(report, "branches"))
case "shareholding":
renderPDFShareholding(pdf, fontName, s.title, getMap(report, "shareholding"))
case "controller":
renderPDFController(pdf, fontName, s.title, getMap(report, "controller"))
case "beneficiaries":
renderPDFBeneficiaries(pdf, fontName, s.title, getSlice(report, "beneficiaries"))
case "investments":
renderPDFInvestments(pdf, fontName, s.title, getMap(report, "investments"))
case "guarantees":
renderPDFGuarantees(pdf, fontName, s.title, getSlice(report, "guarantees"))
case "management":
renderPDFManagement(pdf, fontName, s.title, getMap(report, "management"))
case "assets":
renderPDFAssets(pdf, fontName, s.title, getMap(report, "assets"))
case "licenses":
renderPDFLicenses(pdf, fontName, s.title, getMap(report, "licenses"))
case "activities":
renderPDFActivities(pdf, fontName, s.title, getMap(report, "activities"))
case "inspections":
renderPDFInspections(pdf, fontName, s.title, getSlice(report, "inspections"))
case "risks":
renderPDFRisks(pdf, fontName, s.title, getMap(report, "risks"))
case "timeline":
renderPDFTimeline(pdf, fontName, s.title, getSlice(report, "timeline"))
case "listed":
renderPDFListed(pdf, fontName, s.title, getMap(report, "listed"))
}
}
// 输出为字节
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
return nil, fmt.Errorf("输出企业全景报告 PDF 失败: %w", err)
}
return buf.Bytes(), nil
}
// 辅助方法
func getString(m map[string]interface{}, key string) string {
if m == nil {
return ""
}
if v, ok := m[key]; ok && v != nil {
if s, ok := v.(string); ok {
return s
}
return fmt.Sprint(v)
}
return ""
}
func getStringFromMap(root map[string]interface{}, field string, key string) string {
child := getMap(root, field)
return getString(child, key)
}
func getMap(m map[string]interface{}, key string) map[string]interface{} {
if m == nil {
return map[string]interface{}{}
}
if v, ok := m[key]; ok && v != nil {
if mm, ok := v.(map[string]interface{}); ok {
return mm
}
}
return map[string]interface{}{}
}
func getSlice(m map[string]interface{}, key string) []interface{} {
if m == nil {
return nil
}
if v, ok := m[key]; ok && v != nil {
if arr, ok := v.([]interface{}); ok {
return arr
}
}
return nil
}
func writeKeyValue(pdf *gofpdf.Fpdf, fontName, label, value string) {
if value == "" {
return
}
// 计算表格布局:左侧标签列 + 右侧内容列(模块内数据统一用表格框展示)
pageW, pageH := pdf.GetPageSize()
lMargin, _, rMargin, bMargin := pdf.GetMargins()
labelW := 40.0
valueW := pageW - lMargin - rMargin - labelW
// 行高整体拉宽一点
lineH := 10.0
// 当前起始坐标(统一从左边距起,栅格化对齐)
x := lMargin
y := pdf.GetY()
// 预计算内容高度:使用 SplitLines 获取行数,避免长内容导致单元格高度不够
lines := pdf.SplitLines([]byte(value), valueW)
rowH := float64(len(lines)) * lineH
if rowH < lineH {
rowH = lineH
}
// 轻微增加上下留白,避免文字贴边
rowH += 2
// 如剩余空间不足,先换到新页再画整行,避免表格跨页导致内容重叠
if y+rowH > pageH-bMargin {
pdf.AddPage()
y = pdf.GetY()
pdf.SetXY(lMargin, y)
}
// 画出整行的两个单元格边框
// 使用黑色描边,左侧标签单元格填充标题蓝色背景
pdf.SetDrawColor(0, 0, 0)
pdf.SetFillColor(91, 155, 213)
pdf.Rect(x, y, labelW, rowH, "FD") // 标签:填充+描边
pdf.Rect(x+labelW, y, valueW, rowH, "D") // 值:仅描边
// 写入标签单元格
pdf.SetXY(x, y)
pdf.SetFont(fontName, "B", 13)
pdf.SetTextColor(255, 255, 255)
pdf.CellFormat(labelW, rowH, fmt.Sprintf("%s", label), "", 0, "L", false, 0, "")
// 写入值单元格(自动换行)
pdf.SetXY(x+labelW, y)
pdf.SetFont(fontName, "", 13)
pdf.SetTextColor(0, 0, 0)
pdf.MultiCell(valueW, lineH, value, "", "L", false)
// 将光标移动到下一行开头,恢复默认颜色
pdf.SetDrawColor(0, 0, 0)
pdf.SetFillColor(255, 255, 255)
pdf.SetXY(lMargin, y+rowH)
}
func safePlaceholder(s string) string {
if s == "" {
return "-"
}
return s
}
// -------------------------
// 各板块渲染(与 qiye.html 对齐)
// -------------------------
// 风险综合分析(只做风险点一览,与 qiye.html 中 renderRiskOverview 对齐的精简版)
func renderPDFRiskOverview(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
itemsRaw, ok := v["items"].([]interface{})
if !ok || len(itemsRaw) == 0 {
pdf.MultiCell(0, 6, "暂无风险分析数据", "", "L", false)
return
}
for _, it := range itemsRaw {
m, ok := it.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
continue
}
hit := false
if hv, ok := m["hit"]; ok {
if b, ok := hv.(bool); ok {
hit = b
} else {
hit = fmt.Sprint(hv) == "true"
}
}
status := "未命中"
if hit {
status = "命中"
}
pdf.MultiCell(0, 6, fmt.Sprintf("· %s%s", name, status), "", "L", false)
}
}
// 主体概览,对照 qiye.html renderBasic 中的字段顺序,做精简单列展示
func renderPDFBasic(pdf *gofpdf.Fpdf, fontName, title string, basic map[string]interface{}, entName, creditCode string) {
if len(basic) == 0 && entName == "" && creditCode == "" {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 13)
// 基本信息采用条目式行展示,隔行背景色,不使用表格框
rowIndex := 0
writeBasicRow := func(label, val string) {
if val == "" {
return
}
alt := rowIndex%2 == 0
pdfWriteBasicRow(pdf, fontName, label, val, alt)
rowIndex++
}
writeBasicRow("企业名称", entName)
writeBasicRow("统一社会信用代码", safePlaceholder(creditCode))
keys := []string{
"regNo", "orgCode", "entType", "entTypeCode", "entityTypeCode",
"establishDate", "registeredCapital", "regCap", "regCapCurrency",
"regCapCurrencyCode", "regOrg", "regOrgCode", "regProvince", "provinceCode",
"regCity", "regCityCode", "regDistrict", "districtCode", "address",
"postalCode", "legalRepresentative", "compositionForm", "approvedBusinessItem",
"status", "statusCode", "operationPeriodFrom", "operationPeriodTo", "approveDate",
"cancelDate", "revokeDate", "cancelReason", "revokeReason",
"oldNames", "businessScope", "lastAnnuReportYear",
}
for _, k := range keys {
val := getString(basic, k)
if k == "oldNames" && val == "" {
continue
}
if val == "" {
continue
}
writeBasicRow(mapBasicLabel(k), val)
}
}
func mapBasicLabel(k string) string {
switch k {
case "regNo":
return "注册号"
case "orgCode":
return "组织机构代码"
case "entType":
return "企业类型"
case "entTypeCode":
return "企业类型编码"
case "entityTypeCode":
return "实体类型编码"
case "establishDate":
return "成立日期"
case "registeredCapital", "regCap":
return "注册资本"
case "regCapCurrency":
return "注册资本币种"
case "regCapCurrencyCode":
return "注册资本币种代码"
case "regOrg":
return "登记机关"
case "regOrgCode":
return "注册地址行政编号"
case "regProvince":
return "所在省份"
case "provinceCode":
return "所在省份编码"
case "regCity":
return "所在城市"
case "regCityCode":
return "所在城市编码"
case "regDistrict":
return "所在区/县"
case "districtCode":
return "所在区/县编码"
case "address":
return "住址"
case "postalCode":
return "邮编"
case "legalRepresentative":
return "法定代表人"
case "compositionForm":
return "组成形式"
case "approvedBusinessItem":
return "许可经营项目"
case "status":
return "经营状态"
case "statusCode":
return "经营状态编码"
case "operationPeriodFrom":
return "经营期限自"
case "operationPeriodTo":
return "经营期限至"
case "approveDate":
return "核准日期"
case "cancelDate":
return "注销日期"
case "revokeDate":
return "吊销日期"
case "cancelReason":
return "注销原因"
case "revokeReason":
return "吊销原因"
case "oldNames":
return "曾用名"
case "businessScope":
return "经营业务范围"
case "lastAnnuReportYear":
return "最后年检年度"
default:
return k
}
}
func renderPDFBranches(pdf *gofpdf.Fpdf, fontName, title string, branches []interface{}) {
if len(branches) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for i, b := range branches {
m, ok := b.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
name = getString(m, "entName")
}
if name == "" {
continue
}
regNo := getString(m, "regNo")
credit := getString(m, "creditCode")
regOrg := getString(m, "regOrg")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false)
meta := fmt.Sprintf(" 注册号:%s信用代码%s登记机关%s",
safePlaceholder(regNo),
safePlaceholder(credit),
safePlaceholder(regOrg),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
func renderPDFShareholding(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// 汇总(对齐前端 renderShareholding 的 summary 部分)
shareholderCount := getString(v, "shareholderCount")
registeredCapital := getString(v, "registeredCapital")
currency := getString(v, "currency")
topHolderName := getString(v, "topHolderName")
topHolderPercent := getString(v, "topHolderPercent")
top5TotalPercent := getString(v, "top5TotalPercent")
hasEquityPledges := boolToCN(getString(v, "hasEquityPledges"))
if shareholderCount != "" || registeredCapital != "" || currency != "" ||
topHolderName != "" || topHolderPercent != "" || top5TotalPercent != "" || hasEquityPledges != "" {
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "股权汇总", "", "L", false)
pdf.SetFont(fontName, "", 11)
if shareholderCount != "" {
writeKeyValue(pdf, fontName, "股东总数", shareholderCount)
}
if registeredCapital != "" {
writeKeyValue(pdf, fontName, "注册资本", registeredCapital+" 元")
}
if currency != "" {
writeKeyValue(pdf, fontName, "币种", currency)
}
if topHolderName != "" {
writeKeyValue(pdf, fontName, "第一大股东", topHolderName)
}
if topHolderPercent != "" {
writeKeyValue(pdf, fontName, "第一大股东持股", topHolderPercent)
}
if top5TotalPercent != "" {
writeKeyValue(pdf, fontName, "前五大合计", top5TotalPercent)
}
if hasEquityPledges != "" {
writeKeyValue(pdf, fontName, "存在股权出质", hasEquityPledges)
}
}
// 股东明细(股东全量字段的精简版)
if shRaw, ok := v["shareholders"].([]interface{}); ok && len(shRaw) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "股东明细", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, sh := range shRaw {
m, ok := sh.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
continue
}
pct := getString(m, "ownershipPercent")
if pct == "" {
pct = getString(m, "sharePercent")
}
titleLine := fmt.Sprintf("%d. %s", i+1, name)
if pct != "" {
titleLine = fmt.Sprintf("%s持股%s", titleLine, pct)
}
pdf.MultiCell(0, 6, titleLine, "", "L", false)
// 认缴 / 实缴等关键信息
subscribedAmount := getString(m, "subscribedAmount")
subscribedCurrency := getString(m, "subscribedCurrency")
subscribedCurrencyCode := getString(m, "subscribedCurrencyCode")
subscribedDate := getString(m, "subscribedDate")
subscribedMethod := getString(m, "subscribedMethod")
subscribedMethodCode := getString(m, "subscribedMethodCode")
paidAmount := getString(m, "paidAmount")
paidDate := getString(m, "paidDate")
paidMethod := getString(m, "paidMethod")
creditCode := getString(m, "creditCode")
regNo := getString(m, "regNo")
isHistory := boolToCN(getString(m, "isHistory"))
metaParts := []string{}
if subscribedAmount != "" {
sub := "认缴:" + subscribedAmount
if subscribedCurrency != "" || subscribedCurrencyCode != "" {
sub += " " + subscribedCurrency
if subscribedCurrencyCode != "" {
sub += "" + subscribedCurrencyCode + ""
}
}
metaParts = append(metaParts, sub)
}
if subscribedDate != "" {
metaParts = append(metaParts, "认缴日:"+subscribedDate)
}
if subscribedMethod != "" || subscribedMethodCode != "" {
subm := "认缴方式:" + subscribedMethod
if subscribedMethodCode != "" {
subm += "" + subscribedMethodCode + ""
}
metaParts = append(metaParts, subm)
}
if paidAmount != "" {
metaParts = append(metaParts, "实缴:"+paidAmount)
}
if paidDate != "" {
metaParts = append(metaParts, "实缴日:"+paidDate)
}
if paidMethod != "" {
metaParts = append(metaParts, "实缴方式:"+paidMethod)
}
if creditCode != "" {
metaParts = append(metaParts, "股东信用代码:"+creditCode)
}
if regNo != "" {
metaParts = append(metaParts, "股东注册号:"+regNo)
}
if isHistory != "" {
metaParts = append(metaParts, "是否历史:"+isHistory)
}
if len(metaParts) > 0 {
pdf.MultiCell(0, 5, " "+joinWithChineseSemicolon(metaParts), "", "L", false)
}
pdf.Ln(1)
}
}
// 股权变更记录
if changes, ok := v["equityChanges"].([]interface{}); ok && len(changes) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "股权变更记录", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range changes {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
changeDate := getString(m, "changeDate")
shareholderName := getString(m, "shareholderName")
titleLine := fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(changeDate), shareholderName)
pdf.MultiCell(0, 6, titleLine, "", "L", false)
before := getString(m, "percentBefore")
after := getString(m, "percentAfter")
if before != "" || after != "" {
pdf.MultiCell(0, 5, fmt.Sprintf(" 变更前:%s → 变更后:%s", safePlaceholder(before), safePlaceholder(after)), "", "L", false)
}
pdf.Ln(1)
}
}
// 股权出质
if pledges, ok := v["equityPledges"].([]interface{}); ok && len(pledges) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "股权出质", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range pledges {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
regNo := getString(m, "regNo")
pledgor := getString(m, "pledgor")
pledgee := getString(m, "pledgee")
titleLine := fmt.Sprintf("%d. %s %s → %s", i+1, safePlaceholder(regNo), safePlaceholder(pledgor), safePlaceholder(pledgee))
pdf.MultiCell(0, 6, titleLine, "", "L", false)
amount := getString(m, "pledgedAmount")
regDate := getString(m, "regDate")
status := getString(m, "status")
meta := fmt.Sprintf(" 出质数额:%s 元;登记日:%s状态%s",
safePlaceholder(amount),
safePlaceholder(regDate),
safePlaceholder(status),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 实缴出资明细
if paidDetails, ok := v["paidInDetails"].([]interface{}); ok && len(paidDetails) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "实缴出资明细", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range paidDetails {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
investor := getString(m, "investor")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(investor)), "", "L", false)
paidDate := getString(m, "paidDate")
paidMethod := getString(m, "paidMethod")
accumulatedPaid := getString(m, "accumulatedPaid")
meta := fmt.Sprintf(" 日期:%s方式%s累计实缴%s 万元",
safePlaceholder(paidDate),
safePlaceholder(paidMethod),
safePlaceholder(accumulatedPaid),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 认缴出资明细
if subDetails, ok := v["subscribedCapitalDetails"].([]interface{}); ok && len(subDetails) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "认缴出资明细", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range subDetails {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
investor := getString(m, "investor")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(investor)), "", "L", false)
subscribedDate := getString(m, "subscribedDate")
subscribedMethod := getString(m, "subscribedMethod")
accSubscribed := getString(m, "accumulatedSubscribed")
meta := fmt.Sprintf(" 认缴日:%s方式%s累计认缴%s 元",
safePlaceholder(subscribedDate),
safePlaceholder(subscribedMethod),
safePlaceholder(accSubscribed),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
}
func renderPDFController(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
writeKeyValue(pdf, fontName, "标识", getString(v, "id"))
writeKeyValue(pdf, fontName, "姓名/名称", getString(v, "name"))
writeKeyValue(pdf, fontName, "类型", getString(v, "type"))
writeKeyValue(pdf, fontName, "持股比例", getString(v, "percent"))
writeKeyValue(pdf, fontName, "判定依据", getString(v, "reason"))
writeKeyValue(pdf, fontName, "来源", getString(v, "source"))
}
func renderPDFBeneficiaries(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) {
if len(arr) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for i, b := range arr {
m, ok := b.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
continue
}
pct := getString(m, "percent")
line := fmt.Sprintf("%d. %s", i+1, name)
if pct != "" {
line = fmt.Sprintf("%s持股%s", line, pct)
}
pdf.MultiCell(0, 6, line, "", "L", false)
reason := getString(m, "reason")
if reason != "" {
pdf.MultiCell(0, 5, fmt.Sprintf(" 原因:%s", reason), "", "L", false)
}
pdf.Ln(1)
}
}
func renderPDFInvestments(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// 汇总
totalCount := getString(v, "totalCount")
totalAmount := getString(v, "totalAmount")
if totalCount != "" || totalAmount != "" {
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "汇总", "", "L", false)
pdf.SetFont(fontName, "", 11)
if totalCount != "" {
writeKeyValue(pdf, fontName, "对外投资数量", totalCount)
}
if totalAmount != "" {
writeKeyValue(pdf, fontName, "投资总额(万)", totalAmount)
}
}
// 对外投资列表
list := []interface{}{}
if arr, ok := v["list"].([]interface{}); ok {
list = arr
} else if arr, ok := v["entities"].([]interface{}); ok {
list = arr
}
if len(list) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "对外投资列表", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, inv := range list {
m, ok := inv.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "entName")
if name == "" {
name = getString(m, "name")
}
if name == "" {
continue
}
pct := getString(m, "investPercent")
titleLine := fmt.Sprintf("%d. %s", i+1, name)
if pct != "" {
titleLine = fmt.Sprintf("%s持股%s", titleLine, pct)
}
pdf.MultiCell(0, 6, titleLine, "", "L", false)
}
}
}
func renderPDFGuarantees(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) {
if len(arr) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for i, it := range arr {
m, ok := it.(map[string]interface{})
if !ok {
continue
}
mortgagor := getString(m, "mortgagor")
creditor := getString(m, "creditor")
if mortgagor == "" && creditor == "" {
continue
}
titleLine := fmt.Sprintf("%d. %s → %s", i+1, safePlaceholder(mortgagor), safePlaceholder(creditor))
pdf.MultiCell(0, 6, titleLine, "", "L", false)
amount := getString(m, "principalAmount")
kind := getString(m, "principalKind")
guaranteeType := getString(m, "guaranteeType")
periodFrom := getString(m, "periodFrom")
periodTo := getString(m, "periodTo")
meta := fmt.Sprintf(" 主债权数额:%s种类%s保证方式%s期限%s 至 %s",
safePlaceholder(amount),
safePlaceholder(kind),
safePlaceholder(guaranteeType),
safePlaceholder(periodFrom),
safePlaceholder(periodTo),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
func renderPDFManagement(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// 高管列表
if execs, ok := v["executives"].([]interface{}); ok && len(execs) > 0 {
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "高管列表", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range execs {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
continue
}
position := getString(m, "position")
line := fmt.Sprintf("%d. %s", i+1, name)
if position != "" {
line = fmt.Sprintf("%s%s", line, position)
}
pdf.MultiCell(0, 6, line, "", "L", false)
}
}
// 从业与社保
employeeCount := getString(v, "employeeCount")
femaleEmployeeCount := getString(v, "femaleEmployeeCount")
if employeeCount != "" || femaleEmployeeCount != "" {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "从业与社保", "", "L", false)
pdf.SetFont(fontName, "", 11)
if employeeCount != "" {
writeKeyValue(pdf, fontName, "从业人数", employeeCount)
}
if femaleEmployeeCount != "" {
writeKeyValue(pdf, fontName, "女性从业人数", femaleEmployeeCount)
}
}
// 法定代表人其他任职
var others []interface{}
if arr, ok := v["legalRepresentativeOtherPositions"].([]interface{}); ok {
others = arr
} else if arr, ok := v["legalPersonOtherPositions"].([]interface{}); ok {
others = arr
}
if len(others) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "法定代表人其他任职", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, f := range others {
m, ok := f.(map[string]interface{})
if !ok {
continue
}
entName := getString(m, "entName")
if entName == "" {
continue
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, entName), "", "L", false)
parts := []string{}
if pos := getString(m, "position"); pos != "" {
parts = append(parts, "职务:"+pos)
}
if name := getString(m, "name"); name != "" {
parts = append(parts, "姓名:"+name)
}
if st := getString(m, "entStatus"); st != "" {
parts = append(parts, "企业状态:"+st)
}
if cc := getString(m, "creditCode"); cc != "" {
parts = append(parts, "信用代码:"+cc)
}
if rn := getString(m, "regNo"); rn != "" {
parts = append(parts, "注册号:"+rn)
}
if len(parts) > 0 {
pdf.MultiCell(0, 5, " "+joinWithChineseSemicolon(parts), "", "L", false)
}
pdf.Ln(1)
}
}
}
func renderPDFAssets(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
yearsRaw, ok := v["years"].([]interface{})
if !ok || len(yearsRaw) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for _, y := range yearsRaw {
m, ok := y.(map[string]interface{})
if !ok {
continue
}
year := getString(m, "year")
reportDate := getString(m, "reportDate")
titleLine := year
if reportDate != "" {
titleLine = fmt.Sprintf("%s%s", year, reportDate)
}
pdf.MultiCell(0, 6, titleLine, "", "L", false)
meta := fmt.Sprintf(" 资产总额:%s营收%s净利润%s负债%s",
safePlaceholder(getString(m, "assetTotal")),
safePlaceholder(getString(m, "revenueTotal")),
safePlaceholder(getString(m, "netProfit")),
safePlaceholder(getString(m, "liabilityTotal")),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
func renderPDFLicenses(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// 行政许可列表
if permits, ok := v["permits"].([]interface{}); ok && len(permits) > 0 {
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "行政许可列表", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, p := range permits {
m, ok := p.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
continue
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false)
meta := fmt.Sprintf(" 有效期:%s ~ %s许可机关%s%s",
safePlaceholder(getString(m, "valFrom")),
safePlaceholder(getString(m, "valTo")),
safePlaceholder(getString(m, "licAnth")),
safePlaceholder(getString(m, "licItem")),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 许可变更记录
if changes, ok := v["permitChanges"].([]interface{}); ok && len(changes) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "许可变更记录", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, p := range changes {
m, ok := p.(map[string]interface{})
if !ok {
continue
}
changeDate := getString(m, "changeDate")
changeType := getString(m, "changeType")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(changeDate), safePlaceholder(changeType)), "", "L", false)
before := getString(m, "detailBefore")
after := getString(m, "detailAfter")
if before != "" || after != "" {
pdf.MultiCell(0, 5, " 变更前:"+before, "", "L", false)
pdf.MultiCell(0, 5, " 变更后:"+after, "", "L", false)
}
pdf.Ln(1)
}
}
// 知识产权出质(如有则原样列出)
if ipPledges, ok := v["ipPledges"].([]interface{}); ok && len(ipPledges) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "知识产权出质", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, it := range ipPledges {
pdf.MultiCell(0, 5, fmt.Sprintf("%d. %v", i+1, it), "", "L", false)
}
}
}
func renderPDFActivities(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// 招投标
if bids, ok := v["bids"].([]interface{}); ok && len(bids) > 0 {
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "招投标", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, b := range bids {
m, ok := b.(map[string]interface{})
if !ok {
continue
}
titleText := getString(m, "announcetitle")
if titleText == "" {
titleText = getString(m, "ANNOUNCETITLE")
}
if titleText == "" {
titleText = getString(m, "title")
}
if titleText == "" {
continue
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, titleText), "", "L", false)
}
}
// 网站 / 网店
if websites, ok := v["websites"].([]interface{}); ok && len(websites) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "网站 / 网店", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, w := range websites {
m, ok := w.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "websitname")
if name == "" {
name = getString(m, "WEBSITNAME")
}
if name == "" {
name = getString(m, "name")
}
if name == "" {
continue
}
webType := getString(m, "webtype")
if webType == "" {
webType = getString(m, "WEBTYPE")
}
domain := getString(m, "domain")
if domain == "" {
domain = getString(m, "DOMAIN")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, name), "", "L", false)
meta := fmt.Sprintf(" 类型:%s域名%s", safePlaceholder(webType), safePlaceholder(domain))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
}
func renderPDFInspections(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) {
if len(arr) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for i, it := range arr {
m, ok := it.(map[string]interface{})
if !ok {
continue
}
date := getString(m, "inspectDate")
result := getString(m, "result")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(date), safePlaceholder(result)), "", "L", false)
}
}
func renderPDFRisks(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
// has* 风险项一览(有 / 无)
type item struct {
Key string
Label string
}
items := []item{
{"hasCourtJudgments", "裁判文书"},
{"hasJudicialAssists", "司法协助"},
{"hasDishonestDebtors", "失信被执行人"},
{"hasLimitHighDebtors", "限高被执行人"},
{"hasAdminPenalty", "行政处罚"},
{"hasException", "经营异常"},
{"hasSeriousIllegal", "严重违法"},
{"hasTaxOwing", "欠税"},
{"hasSeriousTaxIllegal", "重大税收违法"},
{"hasMortgage", "动产抵押"},
{"hasEquityPledges", "股权出质"},
{"hasQuickCancel", "简易注销"},
}
for _, it := range items {
val := getString(v, it.Key)
status := "未发现"
if val == "true" || val == "1" {
status = "存在"
}
pdf.MultiCell(0, 6, fmt.Sprintf("· %s%s", it.Label, status), "", "L", false)
}
// 裁判文书
if arr, ok := v["courtJudgments"].([]interface{}); !ok || len(arr) == 0 {
// 兼容 judicialCases 字段
if alt, ok2 := v["judicialCases"].([]interface{}); ok2 {
arr = alt
ok = len(arr) > 0
}
}
if arr, ok := v["courtJudgments"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "裁判文书", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, c := range arr {
m, ok := c.(map[string]interface{})
if !ok {
continue
}
caseNo := getString(m, "caseNumber")
if caseNo == "" {
caseNo = getString(m, "CASENUMBER")
}
if caseNo == "" {
caseNo = getString(m, "judicialDocumentId")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. 案号:%s", i+1, safePlaceholder(caseNo)), "", "L", false)
}
}
// 司法协助
if arr, ok := v["judicialAssists"].([]interface{}); !ok || len(arr) == 0 {
if alt, ok2 := v["judicialAids"].([]interface{}); ok2 {
arr = alt
ok = len(arr) > 0
}
if ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "司法协助", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, a := range arr {
m, ok := a.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "iname")
if name == "" {
name = getString(m, "INAME")
}
if name == "" {
name = getString(m, "marketName")
}
court := getString(m, "courtName")
if court == "" {
court = getString(m, "COURTNAME")
}
share := getString(m, "shaream")
if share == "" {
share = getString(m, "SHAREAM")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false)
meta := fmt.Sprintf(" 法院:%s股权数额%s 元", safePlaceholder(court), safePlaceholder(share))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
}
// 失信被执行人
if arr, ok := v["dishonestDebtors"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "失信被执行人", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, d := range arr {
m, ok := d.(map[string]interface{})
if !ok {
continue
}
caseNo := getString(m, "caseNo")
execCourt := getString(m, "execCourt")
performance := getString(m, "performanceStatus")
publishDate := getString(m, "publishDate")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. 案号 %s", i+1, safePlaceholder(caseNo)), "", "L", false)
meta := fmt.Sprintf(" 执行法院:%s履行情况%s发布时间%s",
safePlaceholder(execCourt),
safePlaceholder(performance),
safePlaceholder(publishDate),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 限高被执行人
if arr, ok := v["limitHighDebtors"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "限高被执行人", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, x := range arr {
m, ok := x.(map[string]interface{})
if !ok {
continue
}
ah := getString(m, "ah")
if ah == "" {
ah = getString(m, "caseNo")
}
zxfy := getString(m, "zxfy")
if zxfy == "" {
zxfy = getString(m, "execCourt")
}
fbrq := getString(m, "fbrq")
if fbrq == "" {
fbrq = getString(m, "publishDate")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(ah)), "", "L", false)
meta := fmt.Sprintf(" 执行法院:%s发布日期%s", safePlaceholder(zxfy), safePlaceholder(fbrq))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 行政处罚
if arr, ok := v["adminPenalties"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "行政处罚", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, p := range arr {
m, ok := p.(map[string]interface{})
if !ok {
continue
}
pendecno := getString(m, "pendecno")
if pendecno == "" {
pendecno = getString(m, "PENDECNO")
}
illeg := getString(m, "illegacttype")
if illeg == "" {
illeg = getString(m, "ILLEGACTTYPE")
}
auth := getString(m, "penauth")
if auth == "" {
auth = getString(m, "PENAUTH")
}
date := getString(m, "pendecissdate")
if date == "" {
date = getString(m, "PENDECISSDATE")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(pendecno)), "", "L", false)
meta := fmt.Sprintf(" 违法类型:%s机关%s日期%s", safePlaceholder(illeg), safePlaceholder(auth), safePlaceholder(date))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 行政处罚变更
if arr, ok := v["adminPenaltyUpdates"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "行政处罚变更记录", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, p := range arr {
m, ok := p.(map[string]interface{})
if !ok {
continue
}
date := getString(m, "updateDate")
content := getString(m, "updateContent")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(date)), "", "L", false)
if content != "" {
pdf.MultiCell(0, 5, " "+content, "", "L", false)
}
pdf.Ln(1)
}
}
// 经营异常
if arr, ok := v["exceptions"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "经营异常", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, e := range arr {
m, ok := e.(map[string]interface{})
if !ok {
continue
}
indate := getString(m, "indate")
if indate == "" {
indate = getString(m, "INDATE")
}
inreason := getString(m, "inreason")
if inreason == "" {
inreason = getString(m, "INREASON")
}
outdate := getString(m, "outdate")
if outdate == "" {
outdate = getString(m, "OUTDATE")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(indate)), "", "L", false)
meta := fmt.Sprintf(" 原因:%s移出%s", safePlaceholder(inreason), safePlaceholder(outdate))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 动产抵押
if arr, ok := v["mortgages"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "动产抵押", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, m := range arr {
mm, ok := m.(map[string]interface{})
if !ok {
continue
}
regNo := getString(mm, "regNo")
amount := getString(mm, "guaranteedAmount")
regDate := getString(mm, "regDate")
regOrg := getString(mm, "regOrg")
status := getString(mm, "status")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(regNo), safePlaceholder(amount)), "", "L", false)
meta := fmt.Sprintf(" 登记日:%s机关%s状态%s", safePlaceholder(regDate), safePlaceholder(regOrg), safePlaceholder(status))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
// 简易注销
if qc, ok := v["quickCancel"].(map[string]interface{}); ok && len(qc) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "简易注销", "", "L", false)
pdf.SetFont(fontName, "", 11)
writeKeyValue(pdf, fontName, "企业名称", getString(qc, "entName"))
writeKeyValue(pdf, fontName, "统一社会信用代码", getString(qc, "creditCode"))
writeKeyValue(pdf, fontName, "注册号", getString(qc, "regNo"))
writeKeyValue(pdf, fontName, "登记机关", getString(qc, "regOrg"))
writeKeyValue(pdf, fontName, "公告起始日期", getString(qc, "noticeFromDate"))
writeKeyValue(pdf, fontName, "公告结束日期", getString(qc, "noticeToDate"))
writeKeyValue(pdf, fontName, "注销结果", getString(qc, "cancelResult"))
}
// 清算信息
if liq, ok := v["liquidation"].(map[string]interface{}); ok && len(liq) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "清算信息", "", "L", false)
pdf.SetFont(fontName, "", 11)
writeKeyValue(pdf, fontName, "负责人", getString(liq, "principal"))
writeKeyValue(pdf, fontName, "成员", getString(liq, "members"))
}
// 纳税与欠税taxRecords
if tax, ok := v["taxRecords"].(map[string]interface{}); ok && len(tax) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "纳税与欠税", "", "L", false)
pdf.SetFont(fontName, "", 11)
ayears := getSlice(tax, "taxLevelAYears")
owings := getSlice(tax, "taxOwings")
hint := fmt.Sprintf("纳税A级年度%s欠税%s",
formatCountHint(len(ayears)),
formatCountHint(len(owings)),
)
pdf.MultiCell(0, 5, hint, "", "L", false)
if len(owings) > 0 {
pdf.Ln(1)
for i, t := range owings {
m, ok := t.(map[string]interface{})
if !ok {
continue
}
taxType := getString(m, "taxOwedType")
if taxType == "" {
taxType = getString(m, "taxowedtype")
}
total := getString(m, "totalOwedAmount")
if total == "" {
total = getString(m, "totalowedamount")
}
pub := getString(m, "publishDate")
if pub == "" {
pub = getString(m, "publishdate")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(taxType)), "", "L", false)
meta := fmt.Sprintf(" 合计:%s公示日%s", safePlaceholder(total), safePlaceholder(pub))
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
} else {
// 完全无 taxRecords 时,与 HTML 行为一致给一个“无”的提示
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "纳税与欠税", "", "L", false)
pdf.SetFont(fontName, "", 11)
pdf.MultiCell(0, 5, "无", "", "L", false)
}
// 涉诉案件litigation—— 按案件类型分类
if lit, ok := v["litigation"].(map[string]interface{}); ok && len(lit) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "涉诉案件汇总", "", "L", false)
pdf.SetFont(fontName, "", 11)
typeLabels := map[string]string{
"administrative": "行政案件",
"implement": "执行案件",
"preservation": "非诉保全审查",
"civil": "民事案件",
"criminal": "刑事案件",
"bankrupt": "强制清算与破产案件",
"jurisdict": "管辖案件",
"compensate": "赔偿案件",
}
// 汇总表
for key, label := range typeLabels {
if sec, ok := lit[key].(map[string]interface{}); ok {
count := getString(sec, "count")
if count != "" && count != "0" {
pdf.MultiCell(0, 5, fmt.Sprintf("· %s%s 件", label, count), "", "L", false)
}
}
}
// 各类案件明细
for key, label := range typeLabels {
sec, ok := lit[key].(map[string]interface{})
if !ok {
continue
}
cases, ok := sec["cases"].([]interface{})
if !ok || len(cases) == 0 {
continue
}
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, label, "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, c := range cases {
m, ok := c.(map[string]interface{})
if !ok {
continue
}
caseNo := getString(m, "caseNo")
court := getString(m, "court")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s%s", i+1, safePlaceholder(caseNo), safePlaceholder(court)), "", "L", false)
region := getString(m, "region")
trialLevel := getString(m, "trialLevel")
caseType := getString(m, "caseType")
cause := getString(m, "cause")
amount := getString(m, "amount")
filing := getString(m, "filingDate")
judgment := getString(m, "judgmentDate")
victory := getString(m, "victoryResult")
meta := fmt.Sprintf(" 地区:%s审级%s案件类型%s案由%s标的金额%s立案日期%s裁判日期%s胜败结果%s",
safePlaceholder(region),
safePlaceholder(trialLevel),
safePlaceholder(caseType),
safePlaceholder(cause),
safePlaceholder(amount),
safePlaceholder(filing),
safePlaceholder(judgment),
safePlaceholder(victory),
)
pdf.MultiCell(0, 5, meta, "", "L", false)
pdf.Ln(1)
}
}
}
}
func renderPDFTimeline(pdf *gofpdf.Fpdf, fontName, title string, arr []interface{}) {
if len(arr) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
for i, it := range arr {
m, ok := it.(map[string]interface{})
if !ok {
continue
}
date := getString(m, "date")
tp := getString(m, "type")
desc := getString(m, "title")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s %s", i+1, safePlaceholder(date), safePlaceholder(tp)), "", "L", false)
if desc != "" {
pdf.MultiCell(0, 5, " "+desc, "", "L", false)
}
pdf.Ln(1)
}
}
func renderPDFListed(pdf *gofpdf.Fpdf, fontName, title string, v map[string]interface{}) {
if len(v) == 0 {
return
}
pdf.AddPage()
pdfSectionTitle(pdf, fontName, title)
pdf.Ln(1)
pdf.SetFont(fontName, "", 11)
isListed := boolToCN(getString(v, "isListed"))
if isListed != "" {
writeKeyValue(pdf, fontName, "是否上市", isListed)
}
// 上市公司信息
if company, ok := v["company"].(map[string]interface{}); ok && len(company) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "上市公司信息", "", "L", false)
pdf.SetFont(fontName, "", 11)
writeKeyValue(pdf, fontName, "经营范围", getString(company, "bizScope"))
writeKeyValue(pdf, fontName, "信用代码", getString(company, "creditCode"))
writeKeyValue(pdf, fontName, "注册地址", getString(company, "regAddr"))
writeKeyValue(pdf, fontName, "注册资本", getString(company, "regCapital"))
writeKeyValue(pdf, fontName, "组织机构代码", getString(company, "orgCode"))
writeKeyValue(pdf, fontName, "币种", getString(company, "cur"))
writeKeyValue(pdf, fontName, "币种名称", getString(company, "curName"))
}
// 股票信息
if stock, ok := v["stock"]; ok && stock != nil {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "股票信息", "", "L", false)
pdf.SetFont(fontName, "", 11)
switch s := stock.(type) {
case string:
if s != "" {
pdf.MultiCell(0, 5, s, "", "L", false)
}
case map[string]interface{}:
// 简单将几个常见字段以表格形式展示
code := getString(s, "code")
name := getString(s, "name")
market := getString(s, "market")
if code != "" {
writeKeyValue(pdf, fontName, "代码", code)
}
if name != "" {
writeKeyValue(pdf, fontName, "名称", name)
}
if market != "" {
writeKeyValue(pdf, fontName, "市场", market)
}
default:
// 其他类型非空时,用字符串方式输出
txt := fmt.Sprint(stock)
if txt != "" && txt != "<nil>" {
pdf.MultiCell(0, 5, txt, "", "L", false)
}
}
}
// 十大股东
if arr, ok := v["topShareholders"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "十大股东", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, s := range arr {
m, ok := s.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
name = getString(m, "shaname")
}
pct := getString(m, "percent")
if pct == "" {
pct = getString(m, "fundedratio")
}
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false)
if pct != "" {
pdf.MultiCell(0, 5, " 持股比例:"+pct, "", "L", false)
}
pdf.Ln(1)
}
}
// 上市高管
if arr, ok := v["listedManagers"].([]interface{}); ok && len(arr) > 0 {
pdf.Ln(2)
pdf.SetFont(fontName, "B", 12)
pdf.MultiCell(0, 6, "上市高管", "", "L", false)
pdf.SetFont(fontName, "", 11)
for i, mng := range arr {
m, ok := mng.(map[string]interface{})
if !ok {
continue
}
name := getString(m, "name")
if name == "" {
name = getString(m, "perName")
}
position := getString(m, "position")
pdf.MultiCell(0, 6, fmt.Sprintf("%d. %s", i+1, safePlaceholder(name)), "", "L", false)
if position != "" {
pdf.MultiCell(0, 5, " 职务:"+position, "", "L", false)
}
pdf.Ln(1)
}
}
}
// joinWithChineseSemicolon 使用中文分号连接字符串
func joinWithChineseSemicolon(parts []string) string {
if len(parts) == 0 {
return ""
}
res := parts[0]
for i := 1; i < len(parts); i++ {
res += "" + parts[i]
}
return res
}
// pdfSectionTitle 渲染模块主标题(对应 HTML h2
func pdfSectionTitle(pdf *gofpdf.Fpdf, fontName, title string) {
pdf.SetFont(fontName, "B", 17)
// 模块标题前加蓝色点缀方块
pdf.SetTextColor(37, 99, 235)
pdf.CellFormat(0, 10, "■ "+title, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
}
// pdfSubTitle 渲染模块内小节标题(对应 HTML h3
func pdfSubTitle(pdf *gofpdf.Fpdf, fontName, title string) {
pdf.SetFont(fontName, "B", 14)
// 小节标题用圆点前缀,略微缩进
pdf.SetTextColor(37, 99, 235)
pdf.CellFormat(0, 8, "● "+title, "", 1, "L", false, 0, "")
pdf.SetTextColor(0, 0, 0)
}
// pdfWriteBasicRow 渲染基础信息的一行,使用隔行背景色而不加表格框
func pdfWriteBasicRow(pdf *gofpdf.Fpdf, fontName, label, value string, alt bool) {
if value == "" {
return
}
pageW, pageH := pdf.GetPageSize()
lMargin, _, rMargin, bMargin := pdf.GetMargins()
x := lMargin
y := pdf.GetY()
w := pageW - lMargin - rMargin
labelW := 40.0
valueW := w - labelW
// 行高整体拉宽一点
lineH := 10.0
// 预先根据内容拆行,计算本行整体高度(用于背景块)
lines := pdf.SplitLines([]byte(value), valueW)
rowH := float64(len(lines)) * lineH
if rowH < lineH {
rowH = lineH
}
rowH += 2
// 如当前页剩余空间不足,先分页再画整行,避免内容与下一页重叠
if y+rowH > pageH-bMargin {
pdf.AddPage()
y = pdf.GetY()
pdf.SetXY(lMargin, y)
}
// 隔行底色
if alt {
pdf.SetFillColor(242, 242, 242)
} else {
pdf.SetFillColor(255, 255, 255)
}
pdf.Rect(x, y, w, rowH, "F")
// 标签列
pdf.SetXY(x, y)
pdf.SetFont(fontName, "B", 14)
pdf.CellFormat(labelW, rowH, fmt.Sprintf("%s", label), "", 0, "L", false, 0, "")
// 值列自动换行
pdf.SetXY(x+labelW, y)
pdf.SetFont(fontName, "", 14)
pdf.MultiCell(valueW, lineH, value, "", "L", false)
// 移到下一行起始位置
pdf.SetXY(lMargin, y+rowH)
}
// formatCountHint 将条数格式化为“X 条”或“无”
func formatCountHint(n int) string {
if n <= 0 {
return "无"
}
return fmt.Sprintf("%d 条", n)
}
// boolToCN 将 true/false/1/0/yes/no 映射成 “是/否”
func boolToCN(s string) string {
if s == "" {
return s
}
low := strings.ToLower(strings.TrimSpace(s))
switch low {
case "true", "1", "yes", "y":
return "是"
case "false", "0", "no", "n":
return "否"
default:
return s
}
}