mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
7a0bbbe8df
commit
b8d90174a0
5 files changed
+159
-24
No files matched your search
@@ -1935,6 +1935,11 @@ body[data-hide-meta] .meta-panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
.invoice-dialog #settingsActions .button {
|
||||||
|
min-width: calc(2em + 18px);
|
||||||
|
padding-inline: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.invoice-dialog #settingsStatus {
|
.invoice-dialog #settingsStatus {
|
||||||
order: 3;
|
order: 3;
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
|
|||||||
+25
-7
@@ -5555,6 +5555,7 @@ async function loadDocumentSettings() {
|
|||||||
settings.busy = true;
|
settings.busy = true;
|
||||||
settingsElements.Form.inert = true;
|
settingsElements.Form.inert = true;
|
||||||
settingsElements.Status.textContent = "正在读取信息";
|
settingsElements.Status.textContent = "正在读取信息";
|
||||||
|
settingsElements.Status.title = "";
|
||||||
try {
|
try {
|
||||||
if (kind === "invoices") { await loadInvoiceSettings(sequence, openSeq); return; }
|
if (kind === "invoices") { await loadInvoiceSettings(sequence, openSeq); return; }
|
||||||
const data = ["bookmarks", "actions", "extensions"].includes(kind) ? await callWASM("ofdgoDocumentNavigation", kind) : {};
|
const data = ["bookmarks", "actions", "extensions"].includes(kind) ? await callWASM("ofdgoDocumentNavigation", kind) : {};
|
||||||
@@ -5742,12 +5743,24 @@ function invoiceSetValue(invoice, field, item, value) {
|
|||||||
target[keys.at(-1)] = value;
|
target[keys.at(-1)] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function invoiceIssues(issues) {
|
function invoiceIssues(issues, details = false) {
|
||||||
return (issues || []).map(issue => typeof issue === "string" ? issue : [issue.field, issue.message || issue.code].filter(Boolean).join(":")).join("\n");
|
const messages = {
|
||||||
|
"unsupported invoice XML root or version": "暂不支持此发票格式或版本",
|
||||||
|
"unsupported eInvoice namespace or version": "暂不支持此发票格式或版本",
|
||||||
|
"unsupported EInvoice version": "暂不支持此发票版本",
|
||||||
|
"EInvoice requires a unique versioned Header": "发票头信息不完整"
|
||||||
|
};
|
||||||
|
return [...new Set((issues || []).map(issue => {
|
||||||
|
const message = typeof issue === "string" ? issue : issue.message || issue.code;
|
||||||
|
if (typeof issue === "string") return details ? message : messages[message] || message;
|
||||||
|
if (details) return [issue.source?.fileLoc || issue.source?.id || issue.source?.kind, issue.source?.nodePath, issue.field, message].filter(Boolean).join(":");
|
||||||
|
return [issue.field, messages[message] || message].filter(Boolean).join(":");
|
||||||
|
}).filter(Boolean))].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderInvoiceSettings() {
|
function renderInvoiceSettings() {
|
||||||
const context = settingsElements.Context, content = settingsElements.Content;
|
const context = settingsElements.Context, content = settingsElements.Content;
|
||||||
|
settingsElements.Status.title = "";
|
||||||
const draft = invoiceCurrent();
|
const draft = invoiceCurrent();
|
||||||
const sources = [...new Map(invoicePanel.entries.map(entry => [invoiceSourceKey(entry.source), entry.source])).entries()];
|
const sources = [...new Map(invoicePanel.entries.map(entry => [invoiceSourceKey(entry.source), entry.source])).entries()];
|
||||||
if (sources.length) settingsSelect(context, "来源", "InvoiceSource", sources.map(([key, source]) => [key, source.fileLoc || source.id || source.kind]), draft?.entry ? invoiceSourceKey(draft.entry.source) : null, value => {
|
if (sources.length) settingsSelect(context, "来源", "InvoiceSource", sources.map(([key, source]) => [key, source.fileLoc || source.id || source.kind]), draft?.entry ? invoiceSourceKey(draft.entry.source) : null, value => {
|
||||||
@@ -5775,6 +5788,7 @@ function renderInvoiceSettings() {
|
|||||||
}
|
}
|
||||||
if (!draft) {
|
if (!draft) {
|
||||||
settingsElements.Status.textContent = invoiceIssues(invoicePanel.issues) || "暂无发票";
|
settingsElements.Status.textContent = invoiceIssues(invoicePanel.issues) || "暂无发票";
|
||||||
|
settingsElements.Status.title = invoiceIssues(invoicePanel.issues, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tabs = document.createElement("div");
|
const tabs = document.createElement("div");
|
||||||
@@ -5821,6 +5835,7 @@ function renderInvoiceSettings() {
|
|||||||
}, !draft.detail.capabilities?.delete, "InvoiceDelete");
|
}, !draft.detail.capabilities?.delete, "InvoiceDelete");
|
||||||
}
|
}
|
||||||
settingsElements.Status.textContent = draft.status || invoiceIssues(draft.detail.issues) || "";
|
settingsElements.Status.textContent = draft.status || invoiceIssues(draft.detail.issues) || "";
|
||||||
|
if (!draft.status) settingsElements.Status.title = invoiceIssues(draft.detail.issues, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function newInvoiceDraft() {
|
async function newInvoiceDraft() {
|
||||||
@@ -5854,10 +5869,12 @@ function renderInvoiceBasic(container, draft) {
|
|||||||
grid.className = "settings-grid";
|
grid.className = "settings-grid";
|
||||||
container.append(title, grid);
|
container.append(title, grid);
|
||||||
for (const field of fields.filter(field => field.group === group)) {
|
for (const field of fields.filter(field => field.group === group)) {
|
||||||
invoiceInput(grid, labels[field.field.split(".").at(-1)] || field.label, `Field_${field.field.replaceAll(".", "_")}`, invoiceValue(draft.invoice, field.field), value => {
|
const value = invoiceValue(draft.invoice, field.field);
|
||||||
|
const display = field.field === "standard" && value === "http://www.edrm.org.cn/schema/e-invoice/2019" ? "电子发票/2019" : value;
|
||||||
|
invoiceInput(grid, labels[field.field.split(".").at(-1)] || field.label, `Field_${field.field.replaceAll(".", "_")}`, display, value => {
|
||||||
invoiceSetValue(draft.invoice, field.field, -1, value);
|
invoiceSetValue(draft.invoice, field.field, -1, value);
|
||||||
invoiceDirty(draft, "model");
|
invoiceDirty(draft, "model");
|
||||||
}, !state.editing || !(field.editable || !draft.entry && field.field === "currency" && draft.invoice.standard !== "EInvoice") || !!draft.entry && !draft.detail.capabilities?.edit).title = field.label;
|
}, !state.editing || !(field.editable || !draft.entry && field.field === "currency" && draft.invoice.standard !== "EInvoice") || !!draft.entry && !draft.detail.capabilities?.edit).title = display !== value ? value : field.label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (draft.entry && state.editing) invoiceInput(container, "同步", "Synchronize", draft.synchronize, value => { draft.synchronize = value; }, !draft.detail.capabilities?.sync, "checkbox").title = draft.detail.capabilities?.reasons?.sync || "同步票面";
|
if (draft.entry && state.editing) invoiceInput(container, "同步", "Synchronize", draft.synchronize, value => { draft.synchronize = value; }, !draft.detail.capabilities?.sync, "checkbox").title = draft.detail.capabilities?.reasons?.sync || "同步票面";
|
||||||
@@ -5926,7 +5943,7 @@ function renderInvoiceAdvanced(container, draft) {
|
|||||||
|
|
||||||
function renderInvoiceItems(container, draft) {
|
function renderInvoiceItems(container, draft) {
|
||||||
const items = draft.invoice.items ||= [];
|
const items = draft.invoice.items ||= [];
|
||||||
const editable = state.editing && (!draft.entry ? invoicePanel.formats.find(value => value.kind === draft.invoice.kind && value.standard === draft.invoice.standard && value.version === draft.invoice.version)?.items : draft.detail.capabilities?.items);
|
const editable = state.editing && (!draft.entry ? invoicePanel.formats.find(value => value.kind === draft.invoice.kind && value.standard === draft.invoice.standard && value.version === draft.invoice.version)?.items : draft.detail.capabilities?.edit && draft.detail.capabilities?.items);
|
||||||
draft.item = Math.max(0, Math.min(draft.item, items.length - 1));
|
draft.item = Math.max(0, Math.min(draft.item, items.length - 1));
|
||||||
const columns = [["name", "名称"], ["specification", "规格"], ["unit", "单位"], ["quantity", "数量"], ["unitPrice", "单价"], ["amount", "金额"], ["taxRate", "税率"], ["taxAmount", "税额"]];
|
const columns = [["name", "名称"], ["specification", "规格"], ["unit", "单位"], ["quantity", "数量"], ["unitPrice", "单价"], ["amount", "金额"], ["taxRate", "税率"], ["taxAmount", "税额"]];
|
||||||
const scroll = document.createElement("div"), table = document.createElement("table");
|
const scroll = document.createElement("div"), table = document.createElement("table");
|
||||||
@@ -6277,10 +6294,11 @@ function resetInvoiceDraft(draft) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderInvoiceXML(container, draft) {
|
function renderInvoiceXML(container, draft) {
|
||||||
|
const writable = state.editing && (!draft.entry || draft.detail.capabilities?.edit);
|
||||||
invoiceInput(container, "原文", "XML", draft.xml, value => {
|
invoiceInput(container, "原文", "XML", draft.xml, value => {
|
||||||
draft.xml = value;
|
draft.xml = value;
|
||||||
invoiceDirty(draft, "xml");
|
invoiceDirty(draft, "xml");
|
||||||
}, !state.editing, "textarea").rows = 14;
|
}, !writable, "textarea").rows = 14;
|
||||||
const actions = document.createElement("div");
|
const actions = document.createElement("div");
|
||||||
actions.className = "invoice-actions";
|
actions.className = "invoice-actions";
|
||||||
container.append(actions);
|
container.append(actions);
|
||||||
@@ -6289,7 +6307,7 @@ function renderInvoiceXML(container, draft) {
|
|||||||
if (!draft.entry) draft.importing = true;
|
if (!draft.entry) draft.importing = true;
|
||||||
invoiceDirty(draft, "xml");
|
invoiceDirty(draft, "xml");
|
||||||
renderDocumentSettings();
|
renderDocumentSettings();
|
||||||
}), false, "InvoiceLoadXML", actions);
|
}), !writable, "InvoiceLoadXML", actions);
|
||||||
settingsButton("导出", async () => {
|
settingsButton("导出", async () => {
|
||||||
const result = await callWASM("ofdgoExportInvoice", JSON.stringify({ ...draft.scope, source: draft.entry.source, index: draft.entry.index }));
|
const result = await callWASM("ofdgoExportInvoice", JSON.stringify({ ...draft.scope, source: draft.entry.source, index: draft.entry.index }));
|
||||||
downloadBytes(result.bytes, "application/xml", (draft.entry.source.fileLoc?.split("/").at(-1) || "invoice.xml"));
|
downloadBytes(result.bytes, "application/xml", (draft.entry.source.fileLoc?.split("/").at(-1) || "invoice.xml"));
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (r *Reader) InvoiceCapabilities(ctx context.Context, source InvoiceSource,
|
|||||||
format, known := invoiceWriteFormat(report.Invoices[index])
|
format, known := invoiceWriteFormat(report.Invoices[index])
|
||||||
result.Create = known && format.Create
|
result.Create = known && format.Create
|
||||||
result.Edit = known && format.Edit
|
result.Edit = known && format.Edit
|
||||||
result.Items = known && format.Items
|
result.Items = result.Edit && format.Items
|
||||||
result.Bind = known
|
result.Bind = known
|
||||||
result.Generate = known
|
result.Generate = known
|
||||||
result.Delete = true
|
result.Delete = true
|
||||||
|
|||||||
@@ -205,10 +205,11 @@ func InvoiceFields(value Invoice) []InvoiceFieldDescriptor {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// InvoiceFormats 返回独立格式能力副本,历史航空格式仅允许读取和原位修改
|
// InvoiceFormats 返回独立格式能力副本,创建及编辑能力按业务版本分别声明
|
||||||
// 返回: 已适配格式列表
|
// 返回: 已适配格式列表
|
||||||
func InvoiceFormats() []InvoiceFormat {
|
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: "EInvoice", 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})
|
||||||
for _, kind := range []string{"inv", "einv", "atr", "rai"} {
|
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"})
|
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"})
|
||||||
}
|
}
|
||||||
@@ -914,6 +915,10 @@ func PatchInvoice(ctx context.Context, data []byte, changes []InvoiceFieldChange
|
|||||||
}
|
}
|
||||||
allowed := map[string]bool{}
|
allowed := map[string]bool{}
|
||||||
for _, inv := range report.Invoices {
|
for _, inv := range report.Invoices {
|
||||||
|
format, supported := invoiceWriteFormat(inv)
|
||||||
|
if !supported || !format.Edit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, f := range inv.Facts {
|
for _, f := range inv.Facts {
|
||||||
if f.Namespace != invoiceXBRL {
|
if f.Namespace != invoiceXBRL {
|
||||||
allowed[f.Source.NodePath] = true
|
allowed[f.Source.NodePath] = true
|
||||||
@@ -1176,6 +1181,9 @@ func UpdateInvoiceXML(ctx context.Context, data []byte, invoiceIndex int, value
|
|||||||
if reflect.DeepEqual(old, value) {
|
if reflect.DeepEqual(old, value) {
|
||||||
return bytes.Clone(data), nil
|
return bytes.Clone(data), nil
|
||||||
}
|
}
|
||||||
|
if format, supported := invoiceWriteFormat(old); !supported || !format.Edit {
|
||||||
|
return nil, fmt.Errorf("invoice format does not support editing")
|
||||||
|
}
|
||||||
if value.Kind != old.Kind || value.Standard != old.Standard || value.Version != old.Version {
|
if value.Kind != old.Kind || value.Standard != old.Standard || value.Version != old.Version {
|
||||||
return nil, fmt.Errorf("invoice update cannot change format")
|
return nil, fmt.Errorf("invoice update cannot change format")
|
||||||
}
|
}
|
||||||
|
|||||||
+119
-15
@@ -31,9 +31,12 @@ import (
|
|||||||
// invoiceXBRL 表示XBRL实例命名空间
|
// invoiceXBRL 表示XBRL实例命名空间
|
||||||
const invoiceXBRL = "http://www.xbrl.org/2003/instance"
|
const invoiceXBRL = "http://www.xbrl.org/2003/instance"
|
||||||
|
|
||||||
// invoiceXSI 表示XML Schema实例命名空间
|
// invoiceXSI 表示XMLSchema实例命名空间
|
||||||
const invoiceXSI = "http://www.w3.org/2001/XMLSchema-instance"
|
const invoiceXSI = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
|
||||||
|
// invoiceEDRM 表示2019版电子发票业务命名空间
|
||||||
|
const invoiceEDRM = "http://www.edrm.org.cn/schema/e-invoice/2019"
|
||||||
|
|
||||||
// invoiceDecimalPattern 仅接受XML十进制词法
|
// invoiceDecimalPattern 仅接受XML十进制词法
|
||||||
var invoiceDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`)
|
var invoiceDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`)
|
||||||
|
|
||||||
@@ -249,7 +252,10 @@ func parseInvoiceXML(ctx context.Context, data []byte, source InvoiceSource) (In
|
|||||||
issue := func(code, field, msg string, s InvoiceSource) {
|
issue := func(code, field, msg string, s InvoiceSource) {
|
||||||
report.Issues = append(report.Issues, InvoiceIssue{Code: code, Field: field, Message: msg, Source: s})
|
report.Issues = append(report.Issues, InvoiceIssue{Code: code, Field: field, Message: msg, Source: s})
|
||||||
}
|
}
|
||||||
if root.name == (xml.Name{Local: "EInvoice"}) {
|
if source.Kind == "tag" && invoiceObjectReferences(root) {
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
if root.name == (xml.Name{Local: "EInvoice"}) || root.name.Local == "eInvoice" && (root.name.Space == "" || root.name.Space == invoiceEDRM) {
|
||||||
return invoicePlainXML(ctx, root, source)
|
return invoicePlainXML(ctx, root, source)
|
||||||
}
|
}
|
||||||
if root.name != (xml.Name{Space: invoiceXBRL, Local: "xbrl"}) {
|
if root.name != (xml.Name{Space: invoiceXBRL, Local: "xbrl"}) {
|
||||||
@@ -955,22 +961,61 @@ func invoiceAmountCheck(inv *Invoice, report *InvoiceReport, source InvoiceSourc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// invoicePlainXML 解析官方数电发票指南中的无命名空间0.3版本
|
// invoiceObjectReferences 识别由对象引用及对象数据构成的票面标引
|
||||||
|
// 入参: root 标引根节点
|
||||||
|
// 返回: bool 是否为纯对象映射
|
||||||
|
func invoiceObjectReferences(root *invoiceXMLNode) bool {
|
||||||
|
count := 0
|
||||||
|
var walk func(*invoiceXMLNode) bool
|
||||||
|
walk = func(n *invoiceXMLNode) bool {
|
||||||
|
if n.name.Space == ofdNamespace2016 || n.name.Space == ofdNamespaceLegacy || n.name.Space == "ofd" || n.name.Space == invoiceEDRM {
|
||||||
|
if n.name.Local == "ObjectRef" {
|
||||||
|
count++
|
||||||
|
return len(n.children) == 0 && strings.TrimSpace(n.text.String()) != "" && invoiceXMLAttr(n, "", "PageRef") != ""
|
||||||
|
}
|
||||||
|
if n.name.Local == "ObjectData" {
|
||||||
|
return len(n.children) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(n.text.String()) != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, child := range n.children {
|
||||||
|
if !walk(child) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return walk(root) && count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// invoicePlainXML 解析数电发票0.3及2019版电子发票XML
|
||||||
// 入参: ctx 上下文,root 根节点,source 来源
|
// 入参: ctx 上下文,root 根节点,source 来源
|
||||||
// 返回: 发票及诊断,取消错误
|
// 返回: 发票及诊断,取消错误
|
||||||
func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSource) (InvoiceReport, error) {
|
func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSource) (InvoiceReport, error) {
|
||||||
report := InvoiceReport{}
|
report := InvoiceReport{}
|
||||||
headers := invoiceChildren(root, "", "Header")
|
|
||||||
if len(headers) != 1 {
|
|
||||||
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Message: "EInvoice requires a unique versioned Header", Source: source})
|
|
||||||
return report, nil
|
|
||||||
}
|
|
||||||
versions := invoiceChildren(headers[0], "", "Version")
|
|
||||||
if len(versions) != 1 || versions[0].text.String() != "0.3" {
|
|
||||||
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Field: "Version", Message: "unsupported EInvoice version", Source: source})
|
|
||||||
return report, nil
|
|
||||||
}
|
|
||||||
inv := Invoice{Kind: "einv", Standard: "EInvoice", Version: "0.3", Sources: []InvoiceSource{source}}
|
inv := Invoice{Kind: "einv", Standard: "EInvoice", 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 {
|
||||||
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Message: "unsupported eInvoice namespace or version", Source: source})
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
inv.Kind, inv.Standard, inv.Version = "inv", invoiceEDRM, "1.0"
|
||||||
|
namespace, itemPath = invoiceEDRM, "/eInvoice/GoodsInfos/GoodsInfo"
|
||||||
|
} else {
|
||||||
|
headers := invoiceChildren(root, "", "Header")
|
||||||
|
if len(headers) != 1 {
|
||||||
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Message: "EInvoice requires a unique versioned Header", Source: source})
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
versions := invoiceChildren(headers[0], "", "Version")
|
||||||
|
if len(versions) != 1 || versions[0].text.String() != "0.3" {
|
||||||
|
report.Issues = append(report.Issues, InvoiceIssue{Code: "unsupported", Field: "Version", Message: "unsupported EInvoice version", Source: source})
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
seen := map[string]InvoiceFact{}
|
seen := map[string]InvoiceFact{}
|
||||||
blocked := map[string]bool{}
|
blocked := map[string]bool{}
|
||||||
var walk func(*invoiceXMLNode, string, *InvoiceItem) error
|
var walk func(*invoiceXMLNode, string, *InvoiceItem) error
|
||||||
@@ -979,10 +1024,10 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path += "/" + n.name.Local
|
path += "/" + n.name.Local
|
||||||
if n.name.Space != "" {
|
if n != root && n.name.Space != namespace {
|
||||||
path = "!" + path
|
path = "!" + path
|
||||||
}
|
}
|
||||||
if path == "/EInvoice/EInvoiceData/IssuItemInformation" {
|
if path == itemPath {
|
||||||
inv.Items = append(inv.Items, InvoiceItem{})
|
inv.Items = append(inv.Items, InvoiceItem{})
|
||||||
item = &inv.Items[len(inv.Items)-1]
|
item = &inv.Items[len(inv.Items)-1]
|
||||||
}
|
}
|
||||||
@@ -1007,6 +1052,9 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dst, numeric := invoicePlainField(&inv, item, path)
|
dst, numeric := invoicePlainField(&inv, item, path)
|
||||||
|
if inv.Standard == invoiceEDRM {
|
||||||
|
dst, numeric = invoiceEDRMField(&inv, item, path)
|
||||||
|
}
|
||||||
if dst == nil {
|
if dst == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1039,11 +1087,67 @@ func invoicePlainXML(ctx context.Context, root *invoiceXMLNode, source InvoiceSo
|
|||||||
if err := walk(root, "", nil); err != nil {
|
if err := walk(root, "", nil); err != nil {
|
||||||
return InvoiceReport{}, err
|
return InvoiceReport{}, err
|
||||||
}
|
}
|
||||||
|
if inv.Standard == invoiceEDRM && inv.Date != "" {
|
||||||
|
if date, err := time.Parse("2006年01月02日", inv.Date); err == nil {
|
||||||
|
inv.Date = date.Format("2006-01-02")
|
||||||
|
}
|
||||||
|
}
|
||||||
invoiceAmountCheck(&inv, &report, source)
|
invoiceAmountCheck(&inv, &report, source)
|
||||||
report.Invoices = append(report.Invoices, inv)
|
report.Invoices = append(report.Invoices, inv)
|
||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invoiceEDRMField 按2019版电子发票路径映射公共字段,合并信息保留原始事实
|
||||||
|
// 入参: inv 发票,item 当前明细,path 命名空间已校验的路径
|
||||||
|
// 返回: 字段地址及数值标志
|
||||||
|
func invoiceEDRMField(inv *Invoice, item *InvoiceItem, path string) (*string, bool) {
|
||||||
|
switch path {
|
||||||
|
case "/eInvoice/InvoiceCode":
|
||||||
|
return &inv.Code, false
|
||||||
|
case "/eInvoice/InvoiceNo":
|
||||||
|
return &inv.Number, false
|
||||||
|
case "/eInvoice/IssueDate":
|
||||||
|
return &inv.Date, false
|
||||||
|
case "/eInvoice/Buyer/BuyerName":
|
||||||
|
return &inv.Buyer.Name, false
|
||||||
|
case "/eInvoice/Buyer/BuyerTaxID":
|
||||||
|
return &inv.Buyer.TaxID, false
|
||||||
|
case "/eInvoice/Seller/SellerName":
|
||||||
|
return &inv.Seller.Name, false
|
||||||
|
case "/eInvoice/Seller/SellerTaxID":
|
||||||
|
return &inv.Seller.TaxID, false
|
||||||
|
case "/eInvoice/TaxExclusiveTotalAmount":
|
||||||
|
return &inv.Amount, true
|
||||||
|
case "/eInvoice/TaxTotalAmount":
|
||||||
|
return &inv.TaxAmount, true
|
||||||
|
case "/eInvoice/TaxInclusiveTotalAmount":
|
||||||
|
return &inv.TotalAmount, true
|
||||||
|
case "/eInvoice/Note":
|
||||||
|
return &inv.Remarks, false
|
||||||
|
}
|
||||||
|
if item != nil && strings.HasPrefix(path, "/eInvoice/GoodsInfos/GoodsInfo/") {
|
||||||
|
switch strings.TrimPrefix(path, "/eInvoice/GoodsInfos/GoodsInfo/") {
|
||||||
|
case "Item":
|
||||||
|
return &item.Name, false
|
||||||
|
case "Specification":
|
||||||
|
return &item.Specification, false
|
||||||
|
case "MeasurementDimension":
|
||||||
|
return &item.Unit, false
|
||||||
|
case "Quantity":
|
||||||
|
return &item.Quantity, true
|
||||||
|
case "Price":
|
||||||
|
return &item.UnitPrice, true
|
||||||
|
case "Amount":
|
||||||
|
return &item.Amount, true
|
||||||
|
case "TaxScheme":
|
||||||
|
return &item.TaxRate, false
|
||||||
|
case "TaxAmount":
|
||||||
|
return &item.TaxAmount, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// invoicePlainField 按官方样例中的完整路径映射字段
|
// invoicePlainField 按官方样例中的完整路径映射字段
|
||||||
// 入参: inv 发票,item 当前明细,path 完整无命名空间路径
|
// 入参: inv 发票,item 当前明细,path 完整无命名空间路径
|
||||||
// 返回: 字段地址及数值标志
|
// 返回: 字段地址及数值标志
|
||||||
|
|||||||
Reference in new issue
Block a user