feat(电子发票): 完善发票识别并统一命名空间
build.yaml / build (push) Successful in 53s

This commit is contained in:
xiaoqidun committed 2026-10-16 15:12:42 +08:00
1 parent 68d6a19656
commit 6639653843
6 files changed
+106 -44

No files matched your search

+1 -2
View File
@@ -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");
+8 -1
View File
@@ -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 导出发票原文
+1 -1
View File
@@ -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...)
+10 -2
View File
@@ -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})
+25 -24
View File
@@ -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, `<xbrli:xbrl xmlns:xbrli="%s" xmlns:xsi="%s" xmlns:iso4217="http://www.xbrl.org/2003/iso4217" xmlns:f="%s" xmlns:link="http://www.xbrl.org/2003/linkbase" xmlns:xlink="http://www.w3.org/1999/xlink">`, invoiceXBRL, invoiceXSI, value.Standard)
fmt.Fprintf(&b, `<xbrli:xbrl xmlns:xbrli="%s" xmlns:xsi="%s" xmlns:iso4217="%s" xmlns:f="%s" xmlns:link="%s" xmlns:xlink="%s">`, invoiceXBRL, invoiceXSI, InvoiceCurrencyNamespace, value.Standard, invoiceLinkbase, invoiceXLink)
fmt.Fprintf(&b, `<link:schemaRef xlink:type="simple" xlink:href="%s"/><xbrli:context id="%s"><xbrli:entity><xbrli:identifier scheme="%s">%s</xbrli:identifier></xbrli:entity><xbrli:period>`, 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 {
+61 -14
View File
@@ -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 {