From 6639653843415e902c8226921816983cba2f3a1a Mon Sep 17 00:00:00 2001 From: xiaoqidun Date: Fri, 16 Oct 2026 15:12:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=94=B5=E5=AD=90=E5=8F=91=E7=A5=A8):=20?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=8F=91=E7=A5=A8=E8=AF=86=E5=88=AB=E5=B9=B6?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/webui/ofdgo.js | 3 +- internal/webui/wasm.go | 9 ++++- ofdgo_editor_invoice.go | 2 +- ofdgo_invoice_source.go | 12 +++++-- ofdgo_invoice_write.go | 49 ++++++++++++++------------- ofdgo_invoice_xml.go | 75 +++++++++++++++++++++++++++++++++-------- 6 files changed, 106 insertions(+), 44 deletions(-) diff --git a/assets/webui/ofdgo.js b/assets/webui/ofdgo.js index 8b691e3..f09f5cd 100644 --- a/assets/webui/ofdgo.js +++ b/assets/webui/ofdgo.js @@ -5857,7 +5857,6 @@ async function newInvoiceDraft() { function renderInvoiceBasic(container, draft) { const fields = (draft.detail.fields || []).filter(field => field.item < 0 && !field.field.startsWith("facts.") && !field.field.startsWith("items.")); - const standards = {"http://www.edrm.org.cn/schema/e-invoice/2019": "电子发票/2019", "http://www.ofdspec.org/2016": "发票标引"}; const labels = { kind: "票种", standard: "规范", version: "版本", number: "号码", code: "代码", date: "日期", currency: "币种", status: "状态", amount: "金额", taxAmount: "税额", totalAmount: "合计", remarks: "备注", @@ -5871,7 +5870,7 @@ function renderInvoiceBasic(container, draft) { container.append(title, grid); for (const field of fields.filter(field => field.group === group)) { const value = invoiceValue(draft.invoice, field.field); - const display = field.field === "standard" ? standards[value] || value : value; + const display = field.field === "standard" ? draft.detail.standardLabel || value : value; invoiceInput(grid, labels[field.field.split(".").at(-1)] || field.label, `Field_${field.field.replaceAll(".", "_")}`, display, value => { invoiceSetValue(draft.invoice, field.field, -1, value); invoiceDirty(draft, "model"); diff --git a/internal/webui/wasm.go b/internal/webui/wasm.go index 3c30706..ac85cb8 100644 --- a/internal/webui/wasm.go +++ b/internal/webui/wasm.go @@ -1004,6 +1004,13 @@ func documentInvoice(args []js.Value) (any, error) { return nil, fmt.Errorf("invoice index out of range") } value := report.Invoices[request.Index] + standardLabel := value.Standard + switch value.Standard { + case ofdgo.InvoiceStandardEDRM2019: + standardLabel = "电子发票/2019" + case ofdgo.InvoiceStandardObjectReference: + standardLabel = "发票标引" + } capabilities, err := r.InvoiceCapabilities(ctx, request.Source, request.Index) if err != nil { return nil, err @@ -1012,7 +1019,7 @@ func documentInvoice(args []js.Value) (any, error) { if bindingErr != nil { report.Issues = append(report.Issues, ofdgo.InvoiceIssue{Code: "binding-error", Message: bindingErr.Error(), Source: request.Source}) } - return map[string]any{"invoice": value, "fields": ofdgo.InvoiceFields(value), "capabilities": capabilities, "bindings": bindings, "digest": ofdgo.InvoiceDigest(data), "xml": string(data), "issues": report.Issues}, nil + return map[string]any{"invoice": value, "standardLabel": standardLabel, "fields": ofdgo.InvoiceFields(value), "capabilities": capabilities, "bindings": bindings, "digest": ofdgo.InvoiceDigest(data), "xml": string(data), "issues": report.Issues}, nil } // exportDocumentInvoice 导出发票原文 diff --git a/ofdgo_editor_invoice.go b/ofdgo_editor_invoice.go index 7421e89..07048cd 100644 --- a/ofdgo_editor_invoice.go +++ b/ofdgo_editor_invoice.go @@ -992,7 +992,7 @@ func invoicePayloadSignature(ctx context.Context, data []byte) (bool, error) { } node := nodes[len(nodes)-1] nodes = nodes[:len(nodes)-1] - if node.name.Space == "http://www.w3.org/2000/09/xmldsig#" && node.name.Local == "Signature" || root.name.Space == "" && root.name.Local == "EInvoice" && node.parent == root && node.name.Space == "" && node.name.Local == "TaxBureauSignature" { + if node.name.Space == invoiceXMLSignature && node.name.Local == "Signature" || root.name.Space == "" && root.name.Local == "EInvoice" && node.parent == root && node.name.Space == "" && node.name.Local == "TaxBureauSignature" { return true, nil } nodes = append(nodes, node.children...) diff --git a/ofdgo_invoice_source.go b/ofdgo_invoice_source.go index 18f3b5d..45ac9fe 100644 --- a/ofdgo_invoice_source.go +++ b/ofdgo_invoice_source.go @@ -34,7 +34,7 @@ func InvoiceDigest(data []byte) string { return hex.EncodeToString(sum[:]) } -// ReadInvoice 读取当前文档及版本声明的发票,标引按对象引用提取文本 +// ReadInvoice 读取当前文档及版本声明的发票,字段路径相对导出XML,标引按对象引用提取文本 // 入参: ctx 取消上下文, source 精确来源 // 返回: InvoiceReport 发票及诊断, error 来源或读取错误 func (r *Reader) ReadInvoice(ctx context.Context, source InvoiceSource) (InvoiceReport, error) { @@ -43,7 +43,12 @@ func (r *Reader) ReadInvoice(ctx context.Context, source InvoiceSource) (Invoice if err != nil { return InvoiceReport{}, err } - report, err := r.readInvoiceXML(ctx, data, source) + local := source + local.NodePath = "" + report, err := r.readInvoiceXML(ctx, data, local) + for i := range report.Invoices { + report.Invoices[i].Sources = []InvoiceSource{source} + } report.DocumentIndex = r.DocumentIndex() if r.versionInfo != nil { report.Version = r.versionInfo.ID @@ -64,6 +69,9 @@ func (r *Reader) readInvoiceXML(ctx context.Context, data []byte, source Invoice return report, err } if root.name.Space != ofdNamespace2016 || root.name.Local != "root" || !invoiceObjectReferences(root) { + if root.name.Local != "eInvoice" && root.name.Local != "EInvoice" && root.name.Local != "xbrl" { + return InvoiceReport{}, nil + } return report, nil } structure, err := parseStructure(ctx, data, CustomTag{TypeID: structureTypeOfficial, NameSpace: ofdNamespace2016}) diff --git a/ofdgo_invoice_write.go b/ofdgo_invoice_write.go index 0e4aca3..be472a6 100644 --- a/ofdgo_invoice_write.go +++ b/ofdgo_invoice_write.go @@ -117,7 +117,7 @@ type invoiceXMLPatch struct { func InvoiceFields(value Invoice) []InvoiceFieldDescriptor { format, supported := invoiceWriteFormat(value) probe := Invoice{Items: []InvoiceItem{{}}} - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { probe.Number, probe.Date = "editable", "editable" for _, group := range invoicePlainGroups() { for _, name := range strings.Fields(group.value) { @@ -171,7 +171,7 @@ func InvoiceFields(value Invoice) []InvoiceFieldDescriptor { case "amount", "taxAmount", "totalAmount", "items.quantity", "items.unitPrice", "items.amount", "items.taxAmount": d.Type = "decimal" case "items.taxRate": - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { d.Type = "decimal" } } @@ -208,13 +208,14 @@ func InvoiceFields(value Invoice) []InvoiceFieldDescriptor { // InvoiceFormats 返回独立格式能力副本,创建及编辑能力按业务版本分别声明 // 返回: 已适配格式列表 func InvoiceFormats() []InvoiceFormat { - result := []InvoiceFormat{{Kind: "einv", Standard: "EInvoice", Version: "0.3", Read: true, Create: true, Edit: true, Items: true}} + result := []InvoiceFormat{{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Read: true, Create: true, Edit: true, Items: true}} result = append(result, InvoiceFormat{Kind: "inv", Standard: invoiceEDRM, Version: "1.0", Read: true, Items: true}) result = append(result, InvoiceFormat{Kind: "inv", Standard: ofdNamespace2016, Version: "1.0", Read: true}) - for _, kind := range []string{"inv", "einv", "atr", "rai"} { - result = append(result, InvoiceFormat{Kind: kind, Standard: "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/" + kind, Version: "2023-12-31", Read: true, Create: true, Edit: true, Items: kind == "inv" || kind == "atr"}) + for _, standard := range []string{InvoiceStandardVAT2023, InvoiceStandardDigital2023, InvoiceStandardAir2023, InvoiceStandardRail2023} { + kind, version := invoiceTaxonomy(standard) + result = append(result, InvoiceFormat{Kind: kind, Standard: standard, Version: version, Read: true, Create: true, Edit: true, Items: kind == "inv" || kind == "atr"}) } - return append(result, InvoiceFormat{Kind: "atr", Standard: "http://xbrl.mof.gov.cn/taxonomy/2021-11-30/atr", Version: "2021-11-30", Read: true, Edit: true, Items: true}) + return append(result, InvoiceFormat{Kind: "atr", Standard: InvoiceStandardAir2021, Version: "2021-11-30", Read: true, Edit: true, Items: true}, InvoiceFormat{Kind: "inv", Standard: InvoiceStandardVAT2021, Version: "2021-09-30", Read: true, Items: true}) } // DecodeInvoices 提取独立XML或XBRL中的发票,不执行联网或完整XSD验证 @@ -280,7 +281,7 @@ func ValidateInvoice(ctx context.Context, value Invoice) ([]InvoiceIssue, error) add("invalid-date", "Date", "invalid invoice date") } } - if value.Currency != "" && !invoiceCurrencyUnit("{http://www.xbrl.org/2003/iso4217}"+value.Currency) { + if value.Currency != "" && !invoiceCurrencyUnit("{"+InvoiceCurrencyNamespace+"}"+value.Currency) { add("invalid-value", "Currency", "invalid ISO currency code") } for _, field := range []invoiceWriteField{{"Amount", value.Amount}, {"TaxAmount", value.TaxAmount}, {"TotalAmount", value.TotalAmount}} { @@ -303,11 +304,11 @@ func ValidateInvoice(ctx context.Context, value Invoice) ([]InvoiceIssue, error) add("invalid-decimal", fmt.Sprintf("Items[%d].%s", i, field.name), "invalid decimal lexical value") } } - if value.Standard == "EInvoice" && item.TaxRate != "" && invoiceDecimal(item.TaxRate) == nil { + if value.Standard == InvoiceStandardEInvoice && item.TaxRate != "" && invoiceDecimal(item.TaxRate) == nil { add("invalid-decimal", fmt.Sprintf("Items[%d].TaxRate", i), "invalid decimal lexical value") } } - if value.Kind == "inv" || value.Standard == "EInvoice" { + if value.Kind == "inv" || value.Standard == InvoiceStandardEInvoice { for _, total := range []invoiceWriteField{{"Amount", value.Amount}, {"TaxAmount", value.TaxAmount}} { sum := new(big.Rat) complete := len(value.Items) > 0 @@ -459,7 +460,7 @@ func EncodeInvoiceWithOptions(ctx context.Context, value Invoice, options Invoic return nil, err } var data []byte - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { data, err = invoiceEncodePlain(value) if err == nil { data, err = invoiceWritePlainSections(ctx, data, options) @@ -621,7 +622,7 @@ func invoiceEncodeXBRL(value Invoice, options InvoiceEncodingOptions) ([]byte, e } var b strings.Builder b.WriteString(xml.Header) - fmt.Fprintf(&b, ``, invoiceXBRL, invoiceXSI, value.Standard) + fmt.Fprintf(&b, ``, invoiceXBRL, invoiceXSI, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, invoiceXLink) fmt.Fprintf(&b, `%s`, invoiceWriteEscape(options.SchemaRef), options.Context.ID, invoiceWriteEscape(options.Context.Scheme), invoiceWriteEscape(options.Context.Identifier)) if options.Context.Instant != "" { if options.Context.StartDate != "" || options.Context.EndDate != "" { @@ -722,7 +723,7 @@ func invoiceWriteFact(b *strings.Builder, value Invoice, fact InvoiceFact, optio if numeric { unit := "" for _, u := range options.Units { - match := u.Namespace == "http://www.xbrl.org/2003/iso4217" && u.Name == value.Currency + match := u.Namespace == InvoiceCurrencyNamespace && u.Name == value.Currency if !invoiceMonetary(value.Kind, fact.Name) { match = u.Namespace == invoiceXBRL && u.Name == "pure" if fact.Unit != "" { @@ -1215,7 +1216,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value if err != nil { return nil, err } - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { var expanded []invoiceXMLPatch for _, s := range spans { n := s.node @@ -1251,7 +1252,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value clean.Facts = nil clean.Sources = nil clean.Items = append([]InvoiceItem(nil), value.Items...) - if clean.Standard != "EInvoice" { + if clean.Standard != InvoiceStandardEInvoice { for _, name := range invoiceWriteHeader(clean) { previous := invoiceXMLField(&old, old.Kind, name) target := invoiceXMLField(&clean, clean.Kind, name) @@ -1262,7 +1263,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value } for i := range clean.Items { clean.Items[i].Fields = nil - if value.Standard != "EInvoice" && (value.Kind == "atr" || value.Kind == "inv") { + if value.Standard != InvoiceStandardEInvoice && (value.Kind == "atr" || value.Kind == "inv") { for _, f := range value.Items[i].Fields { if !invoiceWriteContains(invoiceWriteItemNames(value.Kind), f.Name) { continue @@ -1291,7 +1292,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value } } var generated []byte - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { generated, err = invoiceEncodePlain(clean) } else { options, e := invoiceWriteOriginalOptions(root, old) @@ -1310,7 +1311,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value var patches []invoiceXMLPatch var trailingGroups []invoiceXMLPatch var itemParent, generatedParent *invoiceXMLNode - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { if len(invoiceChildren(root, "", "EInvoiceData")) != 1 || len(invoiceChildren(root, "", "TaxSupervisionInfo")) != 1 { return nil, fmt.Errorf("invoice requires unique data and supervision groups") } @@ -1425,7 +1426,7 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value var matches []Invoice for _, inv := range after.Invoices { if inv.Kind == value.Kind && inv.Standard == value.Standard { - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { matches = append(matches, inv) continue } @@ -1503,8 +1504,8 @@ func invoiceWriteOriginalOptions(root *invoiceXMLNode, value Invoice) (InvoiceEn } } for _, n := range root.children { - if n.name == (xml.Name{Space: "http://www.xbrl.org/2003/linkbase", Local: "schemaRef"}) && options.SchemaRef == "" { - options.SchemaRef = invoiceXMLAttr(n, "http://www.w3.org/1999/xlink", "href") + if n.name == (xml.Name{Space: invoiceLinkbase, Local: "schemaRef"}) && options.SchemaRef == "" { + options.SchemaRef = invoiceXMLAttr(n, invoiceXLink, "href") } if n.name == (xml.Name{Space: invoiceXBRL, Local: "context"}) && invoiceXMLAttr(n, "", "id") == ref { dims, ok := invoiceContext(n) @@ -1684,7 +1685,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i } space, name := value.Standard, invoiceWriteTuple(value.Kind) names := invoiceWriteItemNames(value.Kind) - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { space = "" name = "IssuItemInformation" names = strings.Fields("ItemName SpecMod MeaUnits Quantity UnPrice Amount TaxRate ComTaxAm") @@ -1731,7 +1732,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i if path == "" { for _, f := range item.Fields { mapped := invoiceItemField(&InvoiceItem{}, value.Kind, f.Name) - if value.Standard == "EInvoice" || !invoiceWriteContains(names, f.Name) { + if value.Standard == InvoiceStandardEInvoice || !invoiceWriteContains(names, f.Name) { return nil, fmt.Errorf("unsupported new invoice item fact %s", f.Name) } if mapped != nil { @@ -1793,7 +1794,7 @@ func invoiceWriteMergeItems(ctx context.Context, data []byte, spans map[string]i if len(ordered) == 0 && len(combined) > 0 { var p invoiceXMLPatch var err error - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { p, err = invoiceWriteOrderedInsert(data, spans, parent, "", name, strings.Fields("SellerInformation BuyerInformation BasicInformation IssuItemInformation SpecificInformation AdditionalInformation"), combined) } else { p, err = invoiceWriteInsert(spans[parent.path], combined, data) @@ -1837,7 +1838,7 @@ func invoiceWriteOrderedInsert(data []byte, spans map[string]invoiceXMLSpan, par // 入参: value 发票 // 返回: 缺少元组字段错误 func invoiceWriteCreation(value Invoice) error { - if value.Standard == "EInvoice" { + if value.Standard == InvoiceStandardEInvoice { return nil } for i, item := range value.Items { diff --git a/ofdgo_invoice_xml.go b/ofdgo_invoice_xml.go index 724d758..bd03599 100644 --- a/ofdgo_invoice_xml.go +++ b/ofdgo_invoice_xml.go @@ -28,14 +28,59 @@ import ( "time" ) -// invoiceXBRL 表示XBRL实例命名空间 -const invoiceXBRL = "http://www.xbrl.org/2003/instance" +// invoiceXMLNamespace 表示XML预定义命名空间 +const invoiceXMLNamespace = "http://www.w3.org/XML/1998/namespace" // invoiceXSI 表示XMLSchema实例命名空间 const invoiceXSI = "http://www.w3.org/2001/XMLSchema-instance" +// invoiceXLink 表示XML链接命名空间 +const invoiceXLink = "http://www.w3.org/1999/xlink" + +// invoiceXMLSignature 表示XML数字签名命名空间 +const invoiceXMLSignature = "http://www.w3.org/2000/09/xmldsig#" + +// invoiceXBRL 表示XBRL实例命名空间 +const invoiceXBRL = "http://www.xbrl.org/2003/instance" + +// invoiceLinkbase 表示XBRL链接库命名空间 +const invoiceLinkbase = "http://www.xbrl.org/2003/linkbase" + +// invoiceDimension 表示XBRL实例维度命名空间 +const invoiceDimension = "http://xbrl.org/2006/xbrldi" + +// InvoiceCurrencyNamespace 表示InvoiceUnit中ISO货币度量使用的命名空间 +const InvoiceCurrencyNamespace = "http://www.xbrl.org/2003/iso4217" + +// InvoiceStandardVAT2023 表示2023版增值税发票分类标准 +const InvoiceStandardVAT2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/inv" + +// InvoiceStandardDigital2023 表示2023版数字化电子发票分类标准 +const InvoiceStandardDigital2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/einv" + +// InvoiceStandardAir2023 表示2023版航空运输电子客票行程单分类标准 +const InvoiceStandardAir2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/atr" + +// InvoiceStandardRail2023 表示2023版铁路电子客票分类标准 +const InvoiceStandardRail2023 = "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/rai" + +// InvoiceStandardVAT2021 表示2021版增值税发票分类标准,仅支持读取 +const InvoiceStandardVAT2021 = "http://xbrl.mof.gov.cn/taxonomy/2021-09-30/inv" + +// InvoiceStandardAir2021 表示2021版航空运输电子客票行程单分类标准 +const InvoiceStandardAir2021 = "http://xbrl.mof.gov.cn/taxonomy/2021-11-30/atr" + +// InvoiceStandardEInvoice 表示无命名空间的EInvoice业务标准,版本由Invoice.Version指定 +const InvoiceStandardEInvoice = "EInvoice" + +// InvoiceStandardObjectReference 表示基于OFD对象引用的只读发票标引 +const InvoiceStandardObjectReference = ofdNamespace2016 + +// InvoiceStandardEDRM2019 表示2019版电子发票业务标准 +const InvoiceStandardEDRM2019 = "http://www.edrm.org.cn/schema/e-invoice/2019" + // invoiceEDRM 表示2019版电子发票业务命名空间 -const invoiceEDRM = "http://www.edrm.org.cn/schema/e-invoice/2019" +const invoiceEDRM = InvoiceStandardEDRM2019 // invoiceDecimalPattern 仅接受XML十进制词法 var invoiceDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`) @@ -150,7 +195,7 @@ func invoiceXMLTree(ctx context.Context, data []byte) (*invoiceXMLNode, error) { return nil, fmt.Errorf("invoice XML exceeds namespace URI limit") } } - n := &invoiceXMLNode{name: v.Name, attrs: v.Attr, parent: current, ns: map[string]string{"xml": "http://www.w3.org/XML/1998/namespace"}, counts: map[xml.Name]int{}, start: d.InputOffset()} + n := &invoiceXMLNode{name: v.Name, attrs: v.Attr, parent: current, ns: map[string]string{"xml": invoiceXMLNamespace}, counts: map[xml.Name]int{}, start: d.InputOffset()} index := 1 if current != nil { for k, v := range current.ns { @@ -223,16 +268,18 @@ func invoiceXMLTree(ctx context.Context, data []byte) (*invoiceXMLNode, error) { // 返回: 票种及版本,未知时为空 func invoiceTaxonomy(ns string) (string, string) { switch ns { - case "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/inv": + case InvoiceStandardVAT2023: return "inv", "2023-12-31" - case "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/einv": + case InvoiceStandardDigital2023: return "einv", "2023-12-31" - case "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/atr": + case InvoiceStandardAir2023: return "atr", "2023-12-31" - case "http://xbrl.mof.gov.cn/taxonomy/2023-12-31/rai": + case InvoiceStandardRail2023: return "rai", "2023-12-31" - case "http://xbrl.mof.gov.cn/taxonomy/2021-11-30/atr": + case InvoiceStandardAir2021: return "atr", "2021-11-30" + case InvoiceStandardVAT2021: + return "inv", "2021-09-30" } return "", "" } @@ -351,7 +398,7 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In if err := ctx.Err(); err != nil { return err } - if n.name.Space == invoiceXBRL || n.name.Space == "http://www.xbrl.org/2003/linkbase" { + if n.name.Space == invoiceXBRL || n.name.Space == invoiceLinkbase { return nil } if len(n.children) > 0 && invoiceXMLAttr(n, "", "contextRef") == "" { @@ -421,7 +468,7 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In return nil } if numeric && invoiceMonetary(kind, fact.Name) && invoiceCurrencyUnit(unitValues[fact.Unit]) { - currency := strings.TrimPrefix(unitValues[fact.Unit], "{http://www.xbrl.org/2003/iso4217}") + currency := strings.TrimPrefix(unitValues[fact.Unit], "{"+InvoiceCurrencyNamespace+"}") cf := fact cf.Value = currency cf.Unit = "" @@ -762,7 +809,7 @@ func invoiceContext(n *invoiceXMLNode) ([]InvoiceDimension, bool) { seen := map[xml.Name]bool{} var walk func(*invoiceXMLNode) walk = func(c *invoiceXMLNode) { - if c.name.Space == "http://xbrl.org/2006/xbrldi" && (c.name.Local == "explicitMember" || c.name.Local == "typedMember") { + if c.name.Space == invoiceDimension && (c.name.Local == "explicitMember" || c.name.Local == "typedMember") { name, ok := invoiceQName(c, invoiceXMLAttr(c, "", "dimension")) if !ok || name.Space == "" || seen[name] { valid = false @@ -905,7 +952,7 @@ func invoiceMonetary(kind, name string) bool { // 入参: unit 展开的单位表示 // 返回: 是否为三位大写币种度量 func invoiceCurrencyUnit(unit string) bool { - const prefix = "{http://www.xbrl.org/2003/iso4217}" + const prefix = "{" + InvoiceCurrencyNamespace + "}" if !strings.HasPrefix(unit, prefix) { return false } @@ -995,7 +1042,7 @@ func invoiceObjectReferences(root *invoiceXMLNode) bool { // 返回: 发票及诊断,取消错误 func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSource) (InvoiceReport, error) { report := InvoiceReport{} - inv := Invoice{Kind: "einv", Standard: "EInvoice", Version: "0.3", Sources: []InvoiceSource{source}} + inv := Invoice{Kind: "einv", Standard: InvoiceStandardEInvoice, Version: "0.3", Sources: []InvoiceSource{source}} namespace, itemPath := "", "/EInvoice/EInvoiceData/IssuItemInformation" if root.name.Local == "eInvoice" { if invoiceXMLAttr(root, "", "Version") != "1.0" || len(invoiceChildren(root, invoiceEDRM, "InvoiceNo")) == 0 {