-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
435 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.