Skip to content

Commit

Permalink
invoice/main
Browse files Browse the repository at this point in the history
  • Loading branch information
PichuChen committed Mar 2, 2024
1 parent dbde350 commit 9b5eb6c
Show file tree
Hide file tree
Showing 8 changed files with 435 additions and 56 deletions.
67 changes: 67 additions & 0 deletions mig/f0401invoice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package mig

import (
"encoding/xml"
"fmt"
)

type F0401Invoice struct {
XMLName xml.Name `xml:"Invoice"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`

Main *InvoiceMain `xml:"Main"`
Details *InvoiceDetail `xml:"Details"`
Amount *InvoiceAmount `xml:"Amount"`
}

// NewF0401Invoice 會回傳一個新的F0401發票,輸入參數有賣方資訊 (seller) 與買方資訊 (buyer)以及發票明細
func NewF0401Invoice(seller *RoleDescription, buyer *RoleDescription, details []*ProductItem) (*F0401Invoice, error) {

ret := &F0401Invoice{
Xmlns: "urn:GEINV:eInvoiceMessage:F0401:4.0",
}

ret.Main = &InvoiceMain{
Seller: seller,
Buyer: buyer,
}

ret.Details = &InvoiceDetail{
ProductItem: details,
}

amount := &InvoiceAmount{}
for _, item := range details {
amount.SalesAmount += item.Amount
}
amount.TotalAmount = amount.SalesAmount
ret.Amount = amount
return ret, nil
}

func (invoice *F0401Invoice) Validate() error {
if invoice.Main == nil {
return fmt.Errorf("發票主要資訊為必填")
}
if err := invoice.Main.Validate(); err != nil {
return err
}
if invoice.Details == nil {
return fmt.Errorf("發票明細為必填")
}
if err := invoice.Details.Validate(); err != nil {
return err
}
if invoice.Amount == nil {
return fmt.Errorf("發票金額為必填")
}
if err := invoice.Amount.Validate(); err != nil {
return err
}
return nil
}

func (f *F0401Invoice) Bytes() ([]byte, error) {
return xml.Marshal(f)
}
24 changes: 24 additions & 0 deletions mig/f0401invoice_detail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mig

import "fmt"

type InvoiceDetail struct {
Text string `xml:",chardata"`
ProductItem []*ProductItem `xml:"ProductItem"`
}

func (block *InvoiceDetail) Validate() error {
if len(block.ProductItem) == 0 {
return nil
}
if len := len(block.ProductItem); len > 9999 {
return fmt.Errorf("發票明細項目數量不得超過9999個,目前為%d", len)
}

for _, item := range block.ProductItem {
if err := item.Validate(); err != nil {
return err
}
}
return nil
}
76 changes: 76 additions & 0 deletions mig/f0401invoice_main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package mig

import "fmt"

// Mig 4.0 的圖 5-1 和 15-1 少放一個 ZeroTaxRateReason

type InvoiceMain struct {
InvoiceNumber string `xml:"InvoiceNumber"`
InvoiceDate string `xml:"InvoiceDate"`
InvoiceTime string `xml:"InvoiceTime"`
Seller *RoleDescription `xml:"Seller"`
Buyer *RoleDescription `xml:"Buyer"`

BuyerRemark string `xml:"BuyerRemark,omitempty"`
MainRemark string `xml:"MainRemark,omitempty"`
CustomerClearanceMark string `xml:"CustomerClearanceMark,omitempty"`
Category string `xml:"Category,omitempty"`
RelateNumber string `xml:"RelateNumber,omitempty"`

InvoiceType string `xml:"InvoiceType"`
DonateMark string `xml:"DonateMark"`

CarrierType string `xml:"CarrierType,omitempty"`
CarrierId1 string `xml:"CarrierId1,omitempty"`
CarrierId2 string `xml:"CarrierId2,omitempty"`
PrintMark string `xml:"PrintMark"`
NPOBAN string `xml:"NPOBAN,omitempty"`
RandomNumber string `xml:"RandomNumber,omitempty"`
BondedAreaConfirm string `xml:"BondedAreaConfirm,omitempty"`

ZeroTaxRateReason string `xml:"ZeroTaxRateReason,omitempty"`
Reserved1 string `xml:"Reserved1,omitempty"`
Reserved2 string `xml:"Reserved2,omitempty"`
}

type A0401InvoiceMain struct {
InvoiceMain
}

func (block *InvoiceMain) Validate() error {
if block.InvoiceNumber == "" {
return fmt.Errorf("發票號碼為必填")
}
// TODO: validate InvoiceNumber in type of InvoiceNumberType

if block.InvoiceDate == "" {
return fmt.Errorf("發票日期為必填")
}
// TODO: validate InvoiceDate in type of DateType

if block.InvoiceTime == "" {
return fmt.Errorf("發票時間為必填")
}
// TODO: validate InvoiceTime in type of TimeType

if block.Seller == nil {
return fmt.Errorf("賣方為必填")
}
if err := block.Seller.Validate(); err != nil {
return fmt.Errorf("賣方資料不符規範: %w", err)
}

if block.Buyer == nil {
return fmt.Errorf("買方為必填")
}
if err := block.Buyer.Validate(); err != nil {
return fmt.Errorf("買方資料不符規範: %w", err)
}

// TODO: validate BuyerRemark in type of BuyerRemarkEnum

if len(block.MainRemark) > 200 {
return fmt.Errorf("發票主要註記長度不得大於200個字元")
}
return nil
}
120 changes: 120 additions & 0 deletions mig/invoice_amount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package mig

import "fmt"

// 在 Mig 4.0 裡面的 Invoice/Amount 有兩種定義,一個是 A0401 開立發泡
// 另一個是 F0401 平台存證開立發票訊息,相同欄位名稱的驗證規則不一定相同
// 舉例來說,A0401 的 SalesAmount 的 fractionDigits 是 0
// 但是 F0401 的 SalesAmount 的 fractionDigits 是 7
// 相同部分的驗證會在 InvoiceAmount 物件被驗證,如果規則有不同時則會被拆分驗證

type InvoiceAmount struct {
Text string `xml:",chardata"`
SalesAmount string `xml:"SalesAmount"`
TaxType string `xml:"TaxType"`
TaxRate string `xml:"TaxRate"`
TaxAmount string `xml:"TaxAmount"`
TotalAmount string `xml:"TotalAmount"`
DiscountAmount string `xml:"DiscountAmount,omitempty"`
OriginalCurrencyAmount string `xml:"OriginalCurrencyAmount,omitempty"`
ExchangeRate string `xml:"ExchangeRate,omitempty"`
Currency string `xml:"Currency,omitempty"`
}

type A0401InvoiceAmount struct {
InvoiceAmount
}

// Deprecated in Mig 4.0
type C0401InvoiceAmount struct {
InvoiceAmount

FreeTaxSalesAmount string `xml:"FreeTaxSalesAmount"`
ZeroTaxSalesAmount string `xml:"ZeroTaxSalesAmount"`
}

type F0401InvoiceAmount struct {
InvoiceAmount

FreeTaxSalesAmount string `xml:"FreeTaxSalesAmount"`
ZeroTaxSalesAmount string `xml:"ZeroTaxSalesAmount"`
}

func (block *InvoiceAmount) Validate() error {
if block.SalesAmount == "" {
return fmt.Errorf("銷售額 (SalesAmount) 為必填")
}

if block.TaxType == "" {
return fmt.Errorf("課稅別 (TaxType) 為必填")
}
// TODO: validate TaxType in TaxTypeEnum

if block.TaxRate == "" {
return fmt.Errorf("稅率 (TaxRate) 為必填")
}
// TODO: validate TaxRate in TaxRateEnum

if block.TaxAmount == "" {
return fmt.Errorf("營業稅額 (TaxAmount) 為必填")
}
// TODO: validate TaxAmount in type of decimal(20,0)

if block.TotalAmount == "" {
return fmt.Errorf("總金額 (TotalAmount) 為必填")
}

if block.OriginalCurrencyAmount != "" {
// TODO: validate OriginalCurrencyAmount in type of decimal(20,7)
}

if block.ExchangeRate != "" {
// TODO: validate ExchangeRate in type of decimal(13,5)
}

if block.Currency != "" {
// TODO: validate Currency in CurrencyCodeEnum
}

return nil
}

func (block *A0401InvoiceAmount) Validate() error {
err := block.InvoiceAmount.Validate()
if err != nil {
return err
}
// TODO validate SalesAmount in type of decimal(20,0)
// TODO validate TaxAmount in type of decimal(20,0)
// TODO validate TotalAmount in type of decimal(20,0)
// TODO validate DiscountAmount in type of decimal(20,0)

return nil
}

func (block *F0401InvoiceAmount) Validate() error {
err := block.InvoiceAmount.Validate()
if err != nil {
return err
}

// TODO validate SalesAmount in type of decimal(20,7)
if block.FreeTaxSalesAmount == "" {
return fmt.Errorf("免稅銷售額 (FreeTaxSalesAmount) 為必填")
}
// TODO validate FreeTaxSalesAmount in type of decimal(20,7)

if block.ZeroTaxSalesAmount == "" {
return fmt.Errorf("零稅率銷售額 (ZeroTaxSalesAmount) 為必填")
}
// TODO validate ZeroTaxSalesAmount in type of decimal(20,7)

// TODO validate TaxAmount in type of decimal(20,0)
// TODO validate TotalAmount in type of decimal(20,7)
// TODO validate DiscountAmount in type of decimal(20,7)
// TODO validate OriginalCurrencyAmount in type of decimal(20,7)
// TODO validate ExchangeRate in type of decimal(13,5)
// TODO validate Currency in CurrencyCodeEnum

return nil
}
47 changes: 3 additions & 44 deletions mig/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,14 @@ type RoleDescription struct {
RoleRemark string `xml:"RoleRemark,omitempty"`
}

type MigMain struct {
InvoiceNumber string `xml:"InvoiceNumber"`
InvoiceDate string `xml:"InvoiceDate"`
InvoiceTime string `xml:"InvoiceTime"`
Seller RoleDescription `xml:"Seller"`
Buyer RoleDescription `xml:"Buyer"`

InvoiceType string `xml:"InvoiceType"`
DonateMark string `xml:"DonateMark"`
CarrierType string `xml:"CarrierType"`
CarrierId1 string `xml:"CarrierId1"`
CarrierId2 string `xml:"CarrierId2"`
PrintMark string `xml:"PrintMark"`
RandomNumber string `xml:"RandomNumber"`
}

type ProductItem struct {
Text string `xml:",chardata"`
Description string `xml:"Description"`
Quantity string `xml:"Quantity"`
UnitPrice string `xml:"UnitPrice"`
Amount string `xml:"Amount"`
SequenceNumber string `xml:"SequenceNumber"`
RelateNumber string `xml:"RelateNumber"`
}

type MigDetail struct {
Text string `xml:",chardata"`
ProductItem []ProductItem `xml:"ProductItem"`
}
type MigAmount struct {
Text string `xml:",chardata"`
SalesAmount string `xml:"SalesAmount"`
FreeTaxSalesAmount string `xml:"FreeTaxSalesAmount"`
ZeroTaxSalesAmount string `xml:"ZeroTaxSalesAmount"`
TaxType string `xml:"TaxType"`
TaxRate string `xml:"TaxRate"`
TaxAmount string `xml:"TaxAmount"`
TotalAmount string `xml:"TotalAmount"`
}

type MigFile struct {
XMLName xml.Name `xml:"Invoice"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`

Main MigMain `xml:"Main"`
Details MigDetail `xml:"Details"`
Amount MigAmount `xml:"Amount"`
Main *InvoiceMain `xml:"Main"`
Details *InvoiceDetail `xml:"Details"`
Amount *C0401InvoiceAmount `xml:"Amount"`
}

func NewMigFile(b []byte) (*MigFile, error) {
Expand Down
Loading

0 comments on commit 9b5eb6c

Please sign in to comment.