This commit is contained in:
2026-04-07 13:55:50 +08:00
parent 35a2eb03d8
commit d3554e8b44
3 changed files with 863 additions and 27 deletions

View File

@@ -17,9 +17,9 @@ import (
"tyapi-server/internal/domains/api/services/processors"
)
// ProcessQYGLJ1U9Request 企业全景报告处理器:并发调用企业全量(QYGLUY3S)、股权全景(QYGLJ0Q1)、司法涉诉(QYGL5S1I)
// 以及企业年报(QYGLDJ12)、税收违法(QYGL8848)、欠税公告(QYGL7D9A)
// 前三者走 buildReport / map*;后三者在 buildReport 中转为 annualReports、taxViolations、ownTaxNotices 供页面展示
// ProcessQYGLJ1U9Request 企业全景报告处理器:并发调用企业全量(QYGLUY3S)、股权全景(QYGLJ0Q1)、司法涉诉(QYGL5S1I)
// 企业年报(QYGLDJ12)、税收违法(QYGL8848)、欠税公告(QYGL7D9A)
// 单路失败、查无、解析失败时该路按空数据处理并继续合并;仅当合并后的报告仍无任何可展示的企业要素时返回查询为空
func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
// 复用 QYGLUY3S 的入参结构:企业名称/注册号/统一社会信用代码
var p dto.QYGLJ1U9Req
@@ -30,7 +30,7 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 并发调用三个已有处理器
// 并发调用六个子处理器;单路失败或无数据时降级为空结果,仅当合并后仍无任何企业要素时返回查询为空
type apiResult struct {
key string
data map[string]interface{}
@@ -108,12 +108,15 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors
wg.Wait()
close(resultsCh)
var jiguang, judicial, equity map[string]interface{}
var annualReport, taxViolation, taxArrears map[string]interface{}
jiguang := map[string]interface{}{}
judicial := map[string]interface{}{}
equity := map[string]interface{}{}
annualReport := map[string]interface{}{}
taxViolation := map[string]interface{}{}
taxArrears := map[string]interface{}{}
for r := range resultsCh {
if r.err != nil {
// 任一关键数据源异常,则返回系统错误(也可以根据需求做降级)
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("%s 调用失败: %w", r.key, r.err))
if r.err != nil || r.data == nil {
continue
}
switch r.key {
case "jiguangFull":
@@ -130,27 +133,12 @@ func ProcessQYGLJ1U9Request(ctx context.Context, params []byte, deps *processors
taxArrears = r.data
}
}
if jiguang == nil {
jiguang = map[string]interface{}{}
}
if judicial == nil {
judicial = map[string]interface{}{}
}
if equity == nil {
equity = map[string]interface{}{}
}
if annualReport == nil {
annualReport = map[string]interface{}{}
}
if taxViolation == nil {
taxViolation = map[string]interface{}{}
}
if taxArrears == nil {
taxArrears = map[string]interface{}{}
}
// 复用构建逻辑生成企业报告结构(含年报 / 税收违法 / 欠税公告的转化结果)
report := buildReport(jiguang, judicial, equity, annualReport, taxViolation, taxArrears)
if !qyglJ1U9ReportHasSubstantiveData(report) {
return nil, errors.Join(processors.ErrNotFound, errors.New("未查询到可用于生成报告的企业数据"))
}
// 为报告生成唯一编号并缓存,供后续通过编号查看
reportID := saveQYGLReport(report)

View File

@@ -58,9 +58,654 @@ func buildReport(jiguang, judicial, equity, annualRaw, taxViolationRaw, taxArrea
report["annualReports"] = annualReports
report["taxViolations"] = mapTaxViolations(taxViolationRaw)
report["ownTaxNotices"] = mapOwnTaxNotices(taxArrearsRaw)
applyQYGLJ1U9ReportFieldDefaults(report)
return report
}
// applyQYGLJ1U9ReportFieldDefaults 在子数据源缺失时仍保证报告字段齐全:字符串 ""、数值 0、数组 []、对象按约定填空结构(不向客户暴露「缺键」)。
func applyQYGLJ1U9ReportFieldDefaults(report map[string]interface{}) {
if report == nil {
return
}
// 顶层字符串
report["entName"] = str(report["entName"])
report["creditCode"] = str(report["creditCode"])
report["reportTime"] = str(report["reportTime"])
report["basic"] = mergeBasicDefaults(report["basic"])
b, _ := report["basic"].(map[string]interface{})
if str(report["entName"]) == "" {
report["entName"] = str(b["entName"])
}
if str(report["creditCode"]) == "" {
report["creditCode"] = str(b["creditCode"])
}
report["branches"] = ensureSlice(report["branches"])
report["guarantees"] = ensureSlice(report["guarantees"])
report["inspections"] = ensureSlice(report["inspections"])
report["timeline"] = ensureSlice(report["timeline"])
report["beneficiaries"] = ensureSlice(report["beneficiaries"])
report["annualReports"] = ensureSlice(report["annualReports"])
if _, ok := report["basicList"]; !ok {
report["basicList"] = []interface{}{}
} else {
report["basicList"] = ensureSlice(report["basicList"])
}
report["shareholding"] = mergeShareholdingDefaults(report["shareholding"])
report["controller"] = mergeControllerDefaults(report["controller"])
report["investments"] = mergeInvestmentsDefaults(report["investments"])
report["management"] = mergeManagementDefaults(report["management"])
report["assets"] = mergeAssetsDefaults(report["assets"])
report["licenses"] = mergeLicensesDefaults(report["licenses"])
report["activities"] = mergeActivitiesDefaults(report["activities"])
report["risks"] = mergeRisksDefaults(report["risks"])
report["listed"] = mergeListedDefaults(report["listed"])
report["taxViolations"] = mergeTaxViolationsDefaults(report["taxViolations"])
report["ownTaxNotices"] = mergeOwnTaxNoticesDefaults(report["ownTaxNotices"])
if ro, ok := report["riskOverview"].(map[string]interface{}); ok {
report["riskOverview"] = mergeRiskOverviewDefaults(ro)
} else {
report["riskOverview"] = mergeRiskOverviewDefaults(nil)
}
}
func mergeBasicDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
skel := map[string]interface{}{
"entName": "",
"creditCode": "",
"regNo": "",
"orgCode": "",
"entType": "",
"entTypeCode": "",
"entityTypeCode": "",
"establishDate": "",
"registeredCapital": float64(0),
"regCapCurrency": "",
"regCapCurrencyCode": "",
"regOrg": "",
"regOrgCode": "",
"regProvince": "",
"provinceCode": "",
"regCity": "",
"regCityCode": "",
"regDistrict": "",
"districtCode": "",
"address": "",
"postalCode": "",
"legalRepresentative": "",
"compositionForm": "",
"approvedBusinessItem": "",
"status": "",
"statusCode": "",
"operationPeriodFrom": "",
"operationPeriodTo": "",
"approveDate": "",
"cancelDate": "",
"revokeDate": "",
"cancelReason": "",
"revokeReason": "",
"businessScope": "",
"lastAnnuReportYear": "",
"oldNames": []interface{}{},
}
for k, def := range skel {
if _, ok := out[k]; !ok {
out[k] = def
}
}
if out["oldNames"] == nil {
out["oldNames"] = []interface{}{}
}
return out
}
func ensureSlice(v interface{}) []interface{} {
if v == nil {
return []interface{}{}
}
arr, ok := v.([]interface{})
if !ok {
return []interface{}{}
}
return arr
}
func mergeShareholdingDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
skel := map[string]interface{}{
"shareholders": []interface{}{},
"equityChanges": []interface{}{},
"equityPledges": []interface{}{},
"paidInDetails": []interface{}{},
"subscribedCapitalDetails": []interface{}{},
"hasEquityPledges": false,
"shareholderCount": 0,
"registeredCapital": float64(0),
"currency": "",
"topHolderName": "",
"topHolderPercent": float64(0),
"top5TotalPercent": float64(0),
}
for k, def := range skel {
if _, ok := out[k]; !ok {
out[k] = def
}
}
for _, k := range []string{"shareholders", "equityChanges", "equityPledges", "paidInDetails", "subscribedCapitalDetails"} {
out[k] = ensureSlice(out[k])
}
if out["registeredCapital"] == nil {
out["registeredCapital"] = float64(0)
}
if _, ok := out["hasEquityPledges"].(bool); !ok {
out["hasEquityPledges"] = false
}
return out
}
func mergeControllerDefaults(v interface{}) map[string]interface{} {
if m, ok := v.(map[string]interface{}); ok && m != nil {
if m["path"] == nil {
m["path"] = map[string]interface{}{
"nodes": []interface{}{},
"links": []interface{}{},
}
}
if p, ok := m["path"].(map[string]interface{}); ok {
if _, ok := p["nodes"]; !ok {
p["nodes"] = []interface{}{}
}
if _, ok := p["links"]; !ok {
p["links"] = []interface{}{}
}
}
ensureStrKeys(m, []string{"id", "name", "type", "reason", "source"})
if m["percent"] == nil {
m["percent"] = float64(0)
} else if _, ok := m["percent"].(float64); !ok {
m["percent"] = num(m["percent"])
}
return m
}
return map[string]interface{}{
"id": "",
"name": "",
"type": "",
"percent": float64(0),
"path": map[string]interface{}{
"nodes": []interface{}{},
"links": []interface{}{},
},
"reason": "",
"source": "",
}
}
func mergeInvestmentsDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["totalCount"]; !ok {
out["totalCount"] = 0
}
if _, ok := out["totalAmount"]; !ok {
out["totalAmount"] = float64(0)
}
if out["totalAmount"] == nil {
out["totalAmount"] = float64(0)
}
out["list"] = ensureSlice(out["list"])
out["legalRepresentativeInvestments"] = ensureSlice(out["legalRepresentativeInvestments"])
return out
}
func mergeManagementDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["executives"]; !ok {
out["executives"] = []interface{}{}
} else {
out["executives"] = ensureSlice(out["executives"])
}
if _, ok := out["legalRepresentativeOtherPositions"]; !ok {
out["legalRepresentativeOtherPositions"] = []interface{}{}
} else {
out["legalRepresentativeOtherPositions"] = ensureSlice(out["legalRepresentativeOtherPositions"])
}
if _, ok := out["employeeCount"]; !ok || out["employeeCount"] == nil {
out["employeeCount"] = float64(0)
}
if _, ok := out["femaleEmployeeCount"]; !ok || out["femaleEmployeeCount"] == nil {
out["femaleEmployeeCount"] = float64(0)
}
if out["socialSecurity"] == nil {
out["socialSecurity"] = map[string]interface{}{}
}
return out
}
func mergeAssetsDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["years"]; !ok {
out["years"] = []interface{}{}
} else {
out["years"] = ensureSlice(out["years"])
}
return out
}
func mergeLicensesDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
for _, k := range []string{"permits", "permitChanges", "ipPledges", "otherLicenses"} {
if _, ok := out[k]; !ok {
out[k] = []interface{}{}
} else {
out[k] = ensureSlice(out[k])
}
}
return out
}
func mergeActivitiesDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["bids"]; !ok {
out["bids"] = []interface{}{}
} else {
out["bids"] = ensureSlice(out["bids"])
}
if _, ok := out["websites"]; !ok {
out["websites"] = []interface{}{}
} else {
out["websites"] = ensureSlice(out["websites"])
}
return out
}
func litigationTypeKeys() []string {
return []string{
"administrative", "implement", "preservation", "civil", "criminal",
"bankrupt", "jurisdict", "compensate",
}
}
func defaultLitigationShell() map[string]interface{} {
out := map[string]interface{}{"totalCases": 0}
for _, k := range litigationTypeKeys() {
out[k] = map[string]interface{}{
"count": 0,
"cases": []interface{}{},
}
}
return out
}
func mergeLitigationShape(v interface{}) map[string]interface{} {
out := defaultLitigationShell()
if v == nil {
return out
}
m, ok := v.(map[string]interface{})
if !ok {
return out
}
known := map[string]struct{}{}
for _, k := range litigationTypeKeys() {
known[k] = struct{}{}
}
for k, val := range m {
if k == "totalCases" {
out["totalCases"] = intFromAny(val)
continue
}
if _, isCat := known[k]; isCat {
sm, ok := val.(map[string]interface{})
if !ok {
continue
}
out[k] = map[string]interface{}{
"count": intFromAny(sm["count"]),
"cases": ensureSlice(sm["cases"]),
}
continue
}
out[k] = val
}
return out
}
func defaultQuickCancelShell() map[string]interface{} {
return map[string]interface{}{
"entName": "",
"creditCode": "",
"regNo": "",
"regOrg": "",
"noticeFromDate": "",
"noticeToDate": "",
"cancelResult": "",
"dissents": []interface{}{},
}
}
func defaultLiquidationShell() map[string]interface{} {
return map[string]interface{}{
"principal": "",
"members": []interface{}{},
}
}
func mergeRisksDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
boolKeys := []string{
"hasCourtJudgments", "hasJudicialAssists", "hasDishonestDebtors", "hasLimitHighDebtors",
"hasAdminPenalty", "hasException", "hasSeriousIllegal", "hasTaxOwing", "hasSeriousTaxIllegal",
"hasMortgage", "hasEquityPledges", "hasQuickCancel",
}
for _, k := range boolKeys {
if _, ok := out[k]; !ok {
out[k] = false
}
}
if _, ok := out["riskLevel"]; !ok {
out["riskLevel"] = "低"
}
if _, ok := out["riskScore"]; !ok {
out["riskScore"] = 80
}
for _, k := range []string{"dishonestDebtorCount", "limitHighDebtorCount"} {
if _, ok := out[k]; !ok {
out[k] = 0
}
}
for _, k := range []string{
"courtJudgments", "judicialAssists", "dishonestDebtors", "limitHighDebtors",
"adminPenalties", "adminPenaltyUpdates", "exceptions", "seriousIllegals", "mortgages",
} {
if _, ok := out[k]; !ok {
out[k] = []interface{}{}
} else {
out[k] = ensureSlice(out[k])
}
}
out["litigation"] = mergeLitigationShape(out["litigation"])
if out["quickCancel"] == nil {
out["quickCancel"] = defaultQuickCancelShell()
} else if qm, ok := out["quickCancel"].(map[string]interface{}); ok {
dc := defaultQuickCancelShell()
for k, def := range dc {
if _, ok := qm[k]; !ok {
qm[k] = def
}
}
if qm["dissents"] == nil {
qm["dissents"] = []interface{}{}
} else {
qm["dissents"] = ensureSlice(qm["dissents"])
}
out["quickCancel"] = qm
}
if out["liquidation"] == nil {
out["liquidation"] = defaultLiquidationShell()
} else if lm, ok := out["liquidation"].(map[string]interface{}); ok {
dc := defaultLiquidationShell()
for k, def := range dc {
if _, ok := lm[k]; !ok {
lm[k] = def
}
}
if lm["members"] == nil {
lm["members"] = []interface{}{}
} else {
lm["members"] = ensureSlice(lm["members"])
}
out["liquidation"] = lm
}
tr, _ := out["taxRecords"].(map[string]interface{})
if tr == nil {
tr = map[string]interface{}{}
}
for _, k := range []string{"taxLevelAYears", "seriousTaxIllegal", "taxOwings"} {
if _, ok := tr[k]; !ok {
tr[k] = []interface{}{}
} else {
tr[k] = ensureSlice(tr[k])
}
}
out["taxRecords"] = tr
return out
}
// normalizeListedStock 无股票结构时用 JSON null避免前端把 {} 当成有值而 JSON.stringify 出 "{}"。
func normalizeListedStock(v interface{}) interface{} {
if v == nil {
return nil
}
m, ok := v.(map[string]interface{})
if !ok {
return v
}
if len(m) == 0 {
return nil
}
return v
}
func mergeListedDefaults(v interface{}) map[string]interface{} {
if m, ok := v.(map[string]interface{}); ok && m != nil {
if _, ok := m["isListed"].(bool); !ok {
m["isListed"] = false
}
co, _ := m["company"].(map[string]interface{})
if co == nil {
co = map[string]interface{}{}
}
for k, def := range map[string]interface{}{
"bizScope": "", "creditCode": "", "regAddr": "", "regCapital": "",
"orgCode": "", "cur": "", "curName": "",
} {
if _, ok := co[k]; !ok {
co[k] = def
}
}
m["company"] = co
m["stock"] = normalizeListedStock(m["stock"])
if _, ok := m["topShareholders"]; !ok {
m["topShareholders"] = []interface{}{}
} else {
m["topShareholders"] = ensureSlice(m["topShareholders"])
}
if _, ok := m["listedManagers"]; !ok {
m["listedManagers"] = []interface{}{}
} else {
m["listedManagers"] = ensureSlice(m["listedManagers"])
}
return m
}
return map[string]interface{}{
"isListed": false,
"company": map[string]interface{}{
"bizScope": "", "creditCode": "", "regAddr": "", "regCapital": "",
"orgCode": "", "cur": "", "curName": "",
},
"stock": nil,
"topShareholders": []interface{}{},
"listedManagers": []interface{}{},
}
}
func mergeTaxViolationsDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["total"]; !ok {
out["total"] = 0
}
if _, ok := out["items"]; !ok {
out["items"] = []interface{}{}
} else {
out["items"] = ensureSlice(out["items"])
}
return out
}
func mergeOwnTaxNoticesDefaults(v interface{}) map[string]interface{} {
return mergeTaxViolationsDefaults(v)
}
func mergeRiskOverviewDefaults(v interface{}) map[string]interface{} {
out, _ := v.(map[string]interface{})
if out == nil {
out = map[string]interface{}{}
}
if _, ok := out["riskLevel"]; !ok {
out["riskLevel"] = "低"
}
if _, ok := out["riskScore"]; !ok {
out["riskScore"] = 100
}
if _, ok := out["tags"]; !ok {
out["tags"] = []interface{}{}
} else {
out["tags"] = ensureSlice(out["tags"])
}
if _, ok := out["items"]; !ok {
out["items"] = []interface{}{}
} else {
out["items"] = ensureSlice(out["items"])
}
return out
}
func ensureStrKeys(m map[string]interface{}, keys []string) {
if m == nil {
return
}
for _, k := range keys {
if _, ok := m[k]; !ok {
m[k] = ""
} else if m[k] == nil {
m[k] = ""
}
}
}
// qyglJ1U9ReportHasSubstantiveData 判断合并后的报告是否至少含一项可展示的企业要素。
// 当所有子数据源均失败或等价于无数据时返回 false用于 QYGLJ1U9 整体返回「查询为空」。
func qyglJ1U9ReportHasSubstantiveData(report map[string]interface{}) bool {
if report == nil {
return false
}
trim := func(v interface{}) string { return strings.TrimSpace(str(v)) }
basic, _ := report["basic"].(map[string]interface{})
if basic != nil {
if trim(basic["entName"]) != "" || trim(basic["creditCode"]) != "" {
return true
}
}
if trim(report["entName"]) != "" || trim(report["creditCode"]) != "" {
return true
}
if ar, ok := report["annualReports"].([]interface{}); ok && len(ar) > 0 {
return true
}
if tv, ok := report["taxViolations"].(map[string]interface{}); ok {
if intFromAny(tv["total"]) > 0 {
return true
}
if items, ok := tv["items"].([]interface{}); ok && len(items) > 0 {
return true
}
}
if ot, ok := report["ownTaxNotices"].(map[string]interface{}); ok {
if intFromAny(ot["total"]) > 0 {
return true
}
if items, ok := ot["items"].([]interface{}); ok && len(items) > 0 {
return true
}
}
if br, ok := report["branches"].([]interface{}); ok && len(br) > 0 {
return true
}
if bl, ok := report["basicList"].([]interface{}); ok && len(bl) > 0 {
return true
}
if sh, ok := report["shareholding"].(map[string]interface{}); ok {
if arr, ok := sh["shareholders"].([]interface{}); ok && len(arr) > 0 {
return true
}
if intFromAny(sh["shareholderCount"]) > 0 {
return true
}
}
if inv, ok := report["investments"].(map[string]interface{}); ok {
if arr, ok := inv["list"].([]interface{}); ok && len(arr) > 0 {
return true
}
if intFromAny(inv["totalCount"]) > 0 {
return true
}
}
if ben, ok := report["beneficiaries"].([]interface{}); ok && len(ben) > 0 {
return true
}
if tl, ok := report["timeline"].([]interface{}); ok && len(tl) > 0 {
return true
}
if ctl, _ := report["controller"].(map[string]interface{}); ctl != nil && trim(ctl["name"]) != "" {
return true
}
if risks, ok := report["risks"].(map[string]interface{}); ok {
for _, key := range []string{
"dishonestDebtors", "limitHighDebtors", "adminPenalties", "exceptions",
"seriousIllegals", "mortgages", "courtJudgments", "judicialAssists",
} {
if arr, ok := risks[key].([]interface{}); ok && len(arr) > 0 {
return true
}
}
if lit, ok := risks["litigation"].(map[string]interface{}); ok {
for _, v := range lit {
sub, ok := v.(map[string]interface{})
if !ok {
continue
}
if arr, ok := sub["cases"].([]interface{}); ok && len(arr) > 0 {
return true
}
}
}
}
return false
}
// jiguangWithoutYearReportTables 浅拷贝全量 map并去掉企业全量 V2 中与「公示年报」对应的 YEARREPORT* 键。
// 在已接入 QYGLDJ12 且年报列表非空时使用,避免 build 与 HTML 中与「十六、企业年报」重复展示。
func jiguangWithoutYearReportTables(jiguang map[string]interface{}) map[string]interface{} {