diff --git a/mig/buyer.go b/mig/buyer.go new file mode 100644 index 0000000..b80c498 --- /dev/null +++ b/mig/buyer.go @@ -0,0 +1,22 @@ +package mig + +type Buyer struct { + RoleDescription +} + +func NewBuyer() *Buyer { + return &Buyer{ + RoleDescription: RoleDescription{ + Identifier: "0000000000", + }, + } +} + +func (buyer *Buyer) Validate() error { + err := buyer.RoleDescription.Validate() + if err != nil { + return err + } + + return nil +} diff --git a/mig/donate_mark.go b/mig/donate_mark.go new file mode 100644 index 0000000..7f532bc --- /dev/null +++ b/mig/donate_mark.go @@ -0,0 +1,22 @@ +package mig + +import "fmt" + +// DonateMarkEnum 捐贈註記 (表 4-11) +type DonateMarkEnum string + +const ( + // 非捐贈發票 + DonateMarkNo DonateMarkEnum = "0" + // 捐贈發票 + DonateMarkYes DonateMarkEnum = "1" +) + +// Validate 檢查捐贈註記是否符合規範 +func (t DonateMarkEnum) Validate() error { + switch t { + case DonateMarkNo, DonateMarkYes: + return nil + } + return fmt.Errorf("捐贈註記 (DonateMark) 欄位格式錯誤") +} diff --git a/mig/f0401_test.go b/mig/f0401_test.go new file mode 100644 index 0000000..d87a062 --- /dev/null +++ b/mig/f0401_test.go @@ -0,0 +1,132 @@ +package mig + +import ( + "fmt" + "time" +) + +func ExampleNewF0401Invoice() { + // 這是一個範例 + // 這個範例是用來展示如何使用 NewF0401Invoice 來建立一個 F0401 發票物件 + // 這個範例會建立一個空的 F0401 發票物件 + + // 首先,我們需要建立賣方資訊 + seller := NewSeller() + seller.Identifier = "12345678" + seller.Name = "網路書店" + seller.Address = "台北市中正區和平西路一段 1 號" + seller.PersonInCharge = "王小明" + seller.EmailAddress = "wang@example.com" + + // 接著,我們需要建立買方資訊 + buyer := NewBuyer() + buyer.Identifier = "87654321" + buyer.Name = "網路購物者" + buyer.Address = "台北市信義區信義路五段 7 號" + buyer.PersonInCharge = "陳小美" + buyer.EmailAddress = "mei@example.com" + + // 最後,我們需要建立發票明細 + details := []*F0401ProductItem{} + item := NewF0401ProductItem("網紅小遙") + item.RelateNumber = "A1234567890" + item.Quantity = "1" + item.Unit = "個" + item.UnitPrice = "105" + item.Amount = "105" + details = append(details, item) + + item = NewF0401ProductItem("30m USB 3.0 延長線") + item.RelateNumber = "A1234567891" + item.Quantity = "2" + item.Unit = "條" + item.UnitPrice = "210" + item.Amount = "420" + details = append(details, item) + + // 現在,我們可以使用 NewF0401Invoice 來建立一個 F0401 發票物件 + invoice, err := NewF0401Invoice(seller, buyer, details) + if err != nil { + fmt.Println(err) + return + } + invoice.Main.InvoiceNumber = "QQ18927486" + invoice.SetDateAndTime(time.Date(2024, 3, 2, 11, 39, 40, 0, time.Local)) + invoice.Details.FillSequenceNumber() + invoice.FillAmount() + + // 最後,我們可以檢查這個發票是否符合規範 + if err := invoice.Validate(); err != nil { + fmt.Println(err) + return + } + + // Output: + f, err := invoice.Bytes() + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(f)) + + // 這個範例會輸出一個符合 F0401 發票規範的 XML 字串 + // Output: + // + //
+ // A1234567890 + // 20210101 + // 00:00:00 + // + // 12345678 + // 網路書店 + //
台北市中正區和平西路一段 1 號
+ // 王小明 + // + // < + // + //
+ // + // 87654321 + // 網路購物者 + //
台北市信義區信義路五段 7 號
+ // 陳小美 + // + // < + // + //
+ // 07 + // N + // Y + //
+ //
+ // 1 + // + // 網紅小遙 + // 1 + // + // 100 + // 105 + // A1234567890 + // + // 2 + // + // 30m USB 3.0 延長線 + // 1 + // + // 1550 + // 1628 + // A1234567891 + // + //
+ // + // 1733 + // 1 + // 0.05 + // 83 + // 1816 + // 0 + // 0 + // + //
+ +} diff --git a/mig/f0401invoice.go b/mig/f0401invoice.go index a6685f5..4c5df5e 100644 --- a/mig/f0401invoice.go +++ b/mig/f0401invoice.go @@ -3,6 +3,8 @@ package mig import ( "encoding/xml" "fmt" + "math/big" + "time" ) type F0401Invoice struct { @@ -16,7 +18,7 @@ type F0401Invoice struct { } // NewF0401Invoice 會回傳一個新的F0401發票,輸入參數有賣方資訊 (seller) 與買方資訊 (buyer)以及發票明細 -func NewF0401Invoice(seller *RoleDescription, buyer *RoleDescription, details []*F0401ProductItem) (*F0401Invoice, error) { +func NewF0401Invoice(seller *Seller, buyer *Buyer, details []*F0401ProductItem) (*F0401Invoice, error) { ret := &F0401Invoice{ Xmlns: "urn:GEINV:eInvoiceMessage:F0401:4.0", @@ -24,24 +26,83 @@ func NewF0401Invoice(seller *RoleDescription, buyer *RoleDescription, details [] ret.Main = &F0401InvoiceMain{ InvoiceMain: InvoiceMain{ - Seller: seller, - Buyer: buyer, + InvoiceDate: time.Now().Format("20060102"), + InvoiceTime: time.Now().Format("15:04:05"), + Seller: seller, + Buyer: buyer, + InvoiceType: InvoiceTypeGeneral, + DonateMark: DonateMarkNo, }, + PrintMark: "Y", } ret.Details = &F0401InvoiceDetail{ ProductItem: details, } - amount := &F0401InvoiceAmount{} - for _, item := range details { - amount.SalesAmount += item.Amount + amount := &F0401InvoiceAmount{ + InvoiceAmount: InvoiceAmount{ + TaxRate: "0.05", + TaxType: TaxTypeTaxable, + }, + FreeTaxSalesAmount: "0", + ZeroTaxSalesAmount: "0", } - amount.TotalAmount = amount.SalesAmount + ret.Amount = amount return ret, nil } +func (invoice *F0401Invoice) SetDateAndTime(t time.Time) { + invoice.Main.InvoiceDate = t.Format("20060102") + invoice.Main.InvoiceTime = t.Format("15:04:05") +} + +// IsB2C 會回傳發票是否為B2C發票,判斷根據 (表 4-7 BAN 資料元規格) +func (invoice *F0401Invoice) IsB2C() bool { + return invoice.Main.Buyer.Identifier == "0000000000" +} + +// FillAmount 會根據發票明細填入 SalesAmount, TaxAmount, TotalAmount +// SalesAmount 為明細的金額加總 +// 當此發票為 B2B 發票時,TaxAmount 為 SalesAmount * TaxRate (四捨五入至整數) +// 當此發票為 B2C 發票時,TaxAmount 為 0 +// TotalAmount 為 SalesAmount + TaxAmount +func (invoice *F0401Invoice) FillAmount() error { + salesAmount := new(big.Float) + taxAmount := new(big.Float) + for _, item := range invoice.Details.ProductItem { + amount := new(big.Float) + amount, ok := amount.SetString(item.Amount) + if !ok { + return fmt.Errorf("parse amount failed") + } + salesAmount.Add(salesAmount, amount) + } + invoice.Amount.SalesAmount = salesAmount.Text('f', 0) + + if invoice.IsB2C() { + invoice.Amount.TaxAmount = "0" + } else { + taxRate := new(big.Float) + taxRate, ok := taxRate.SetString(invoice.Amount.TaxRate) + if !ok { + return fmt.Errorf("parse tax rate failed") + } + taxAmount := new(big.Float).Mul(salesAmount, taxRate) + + // 四捨五入至整數 + taxAmount = taxAmount.Add(taxAmount, big.NewFloat(0.5)) + bint, _ := taxAmount.Int(nil) + invoice.Amount.TaxAmount = bint.String() + } + taxAmount.SetString(invoice.Amount.TaxAmount) + totalAmount := new(big.Float) + totalAmount.Add(salesAmount, taxAmount) + invoice.Amount.TotalAmount = totalAmount.Text('f', 0) + return nil +} + func (invoice *F0401Invoice) Validate() error { if invoice.Main == nil { return fmt.Errorf("發票主要資訊為必填") @@ -65,5 +126,5 @@ func (invoice *F0401Invoice) Validate() error { } func (f *F0401Invoice) Bytes() ([]byte, error) { - return xml.Marshal(f) + return xml.MarshalIndent(f, "", " ") } diff --git a/mig/invoice_amount.go b/mig/invoice_amount.go index 1cd9410..5c62026 100644 --- a/mig/invoice_amount.go +++ b/mig/invoice_amount.go @@ -9,16 +9,16 @@ import "fmt" // 相同部分的驗證會在 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"` + Text string `xml:",chardata"` + SalesAmount string `xml:"SalesAmount"` + TaxType TaxTypeEnum `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 A0101InvoiceAmount struct { @@ -48,7 +48,10 @@ func (block *InvoiceAmount) Validate() error { if block.TaxType == "" { return fmt.Errorf("課稅別 (TaxType) 為必填") } - // TODO: validate TaxType in TaxTypeEnum + err := block.TaxType.Validate() + if err != nil { + return err + } if block.TaxRate == "" { return fmt.Errorf("稅率 (TaxRate) 為必填") diff --git a/mig/invoice_detail.go b/mig/invoice_detail.go index 5865419..d7c4db6 100644 --- a/mig/invoice_detail.go +++ b/mig/invoice_detail.go @@ -26,9 +26,9 @@ func (block *A0101InvoiceDetail) Validate() error { return fmt.Errorf("發票明細項目數量不得超過9999個,目前為%d", len) } - for _, item := range block.ProductItem { + for i, item := range block.ProductItem { if err := item.Validate(); err != nil { - return err + return fmt.Errorf("第 %d 個發票明細項目驗證錯誤: %v", i+1, err) } } return nil @@ -42,10 +42,22 @@ func (block *F0401InvoiceDetail) Validate() error { return fmt.Errorf("發票明細項目數量不得超過9999個,目前為%d", len) } - for _, item := range block.ProductItem { + for i, item := range block.ProductItem { if err := item.Validate(); err != nil { - return err + return fmt.Errorf("第 %d 個發票明細項目驗證錯誤: %v", i+1, err) } } return nil } + +func (block *A0101InvoiceDetail) FillSequenceNumber() { + for i, item := range block.ProductItem { + item.SequenceNumber = fmt.Sprintf("%d", i+1) + } +} + +func (block *F0401InvoiceDetail) FillSequenceNumber() { + for i, item := range block.ProductItem { + item.SequenceNumber = fmt.Sprintf("%d", i+1) + } +} diff --git a/mig/invoice_main.go b/mig/invoice_main.go index cb8a2f4..58e3757 100644 --- a/mig/invoice_main.go +++ b/mig/invoice_main.go @@ -10,11 +10,11 @@ import ( // InvoiceMain represents the main information of an invoice. type InvoiceMain struct { - InvoiceNumber string `xml:"InvoiceNumber"` - InvoiceDate string `xml:"InvoiceDate"` - InvoiceTime string `xml:"InvoiceTime"` - Seller *RoleDescription `xml:"Seller"` - Buyer *RoleDescription `xml:"Buyer"` + InvoiceNumber string `xml:"InvoiceNumber"` + InvoiceDate string `xml:"InvoiceDate"` + InvoiceTime string `xml:"InvoiceTime"` + Seller *Seller `xml:"Seller"` + Buyer *Buyer `xml:"Buyer"` BuyerRemark string `xml:"BuyerRemark,omitempty"` MainRemark string `xml:"MainRemark,omitempty"` @@ -22,9 +22,9 @@ type InvoiceMain struct { Category string `xml:"Category,omitempty"` RelateNumber string `xml:"RelateNumber,omitempty"` - InvoiceType string `xml:"InvoiceType"` - GroupMark string `xml:"GroupMark,omitempty"` - DonateMark string `xml:"DonateMark"` + InvoiceType InvoiceTypeEnum `xml:"InvoiceType"` + GroupMark string `xml:"GroupMark,omitempty"` + DonateMark DonateMarkEnum `xml:"DonateMark"` ZeroTaxRateReason string `xml:"ZeroTaxRateReason,omitempty"` Reserved1 string `xml:"Reserved1,omitempty"` @@ -114,7 +114,10 @@ func (block *InvoiceMain) Validate() error { if block.InvoiceType == "" { return fmt.Errorf("發票類別 (InvoiceType) 為必填") } - // TODO: validate InvoiceType in InvoiceTypeEnum + err := block.InvoiceType.Validate() + if err != nil { + return fmt.Errorf("發票類別 (InvoiceType) 不符規範: %w", err) + } if len(block.GroupMark) > 1 { return fmt.Errorf("彙開註記 (GroupMark) 長度不得大於1個字元") @@ -124,9 +127,7 @@ func (block *InvoiceMain) Validate() error { return fmt.Errorf("捐贈註記 (DonateMark) 為必填") } - if block.ZeroTaxRateReason == "" { - return fmt.Errorf("零稅率原因 (ZeroTaxRateReason) 為必填") - } + // TODO: validate ZeroTaxRateReason in ZeroTaxRateReasonEnum if len(block.Reserved1) > 20 { return fmt.Errorf("保留欄位 (Reserved1) 長度不得大於20個字元") @@ -198,12 +199,14 @@ func (block *F0401InvoiceMain) Validate() error { return fmt.Errorf("發票捐贈對象 (NPOBAN) 長度不得大於10個字元") } - if len(block.RandomNumber) != 4 { - return fmt.Errorf("發票防偽隨機碼 (RandomNumber) 長度必須為4個字元") - } - pattern := "[0-9]{4}" - if match, _ := regexp.MatchString(pattern, block.RandomNumber); !match { - return fmt.Errorf("發票防偽隨機碼 (RandomNumber) 應為4位數字") + if block.RandomNumber != "" { + if len(block.RandomNumber) != 4 { + return fmt.Errorf("發票防偽隨機碼 (RandomNumber) 長度必須為4個字元") + } + pattern := "[0-9]{4}" + if match, _ := regexp.MatchString(pattern, block.RandomNumber); !match { + return fmt.Errorf("發票防偽隨機碼 (RandomNumber) 應為4位數字") + } } // TODO: validate BondedAreaConfirm in BondedAreaConfirmEnum diff --git a/mig/invoice_type.go b/mig/invoice_type.go new file mode 100644 index 0000000..a17f842 --- /dev/null +++ b/mig/invoice_type.go @@ -0,0 +1,22 @@ +package mig + +import "fmt" + +// InvoiceTypeEnum 電子發票類別 (表 4-4) +type InvoiceTypeEnum string + +const ( + // 一般稅額計算之電子發票 + InvoiceTypeGeneral InvoiceTypeEnum = "07" + // 特種稅額計算之電子發票 + InvoiceTypeSpecial InvoiceTypeEnum = "08" +) + +// Validate 檢查發票類別是否符合規範 +func (t InvoiceTypeEnum) Validate() error { + switch t { + case InvoiceTypeGeneral, InvoiceTypeSpecial: + return nil + } + return fmt.Errorf("發票類別 (InvoiceType) 欄位格式錯誤") +} diff --git a/mig/main.go b/mig/main.go index d0c8ed8..1675fff 100644 --- a/mig/main.go +++ b/mig/main.go @@ -5,18 +5,6 @@ import ( "log" ) -type RoleDescription struct { - Identifier string `xml:"Identifier"` - Name string `xml:"Name"` - Address string `xml:"Address,omitempty"` - PersonInCharge string `xml:"PersonInCharge,omitempty"` - TelephoneNumber string `xml:"TelephoneNumber,omitempty"` - FacsimileNumber string `xml:"FacsimileNumber,omitempty"` - EmailAddress string `xml:"EmailAddress,omitempty"` - CustomerNumber string `xml:"CustomerNumber,omitempty"` - RoleRemark string `xml:"RoleRemark,omitempty"` -} - type C0401Invoice struct { XMLName xml.Name `xml:"Invoice"` Text string `xml:",chardata"` @@ -33,7 +21,7 @@ func NewC0401Invoice(b []byte) (*C0401Invoice, error) { if err := xml.Unmarshal(b, &f); err != nil { log.Fatal(err) } - log.Println("xmlname", f.XMLName.Space, f.XMLName.Local) + // log.Println("xmlname", f.XMLName.Space, f.XMLName.Local) return &f, nil } diff --git a/mig/main_test.go b/mig/main_test.go index b4d4f0d..ea56bcb 100644 --- a/mig/main_test.go +++ b/mig/main_test.go @@ -16,16 +16,20 @@ func TestMarshalC0401(t *testing.T) { InvoiceNumber: "AA00000000", InvoiceDate: "20060102", InvoiceTime: "15:04:05", - Seller: &RoleDescription{ - Identifier: "54834795", - Name: "台灣智慧家庭股份有限公司", - Address: "Address", - PersonInCharge: "PersonInCharge", - EmailAddress: "example@example.com", + Seller: &Seller{ + RoleDescription: RoleDescription{ + Identifier: "54834795", + Name: "台灣智慧家庭股份有限公司", + Address: "Address", + PersonInCharge: "PersonInCharge", + EmailAddress: "example@example.com", + }, }, - Buyer: &RoleDescription{ - Identifier: "0000000000", - Name: "Buyer Name", + Buyer: &Buyer{ + RoleDescription: RoleDescription{ + Identifier: "0000000000", + Name: "Buyer Name", + }, }, InvoiceType: "07", DonateMark: "0", diff --git a/mig/product_item.go b/mig/product_item.go index 3a91de4..032e86b 100644 --- a/mig/product_item.go +++ b/mig/product_item.go @@ -24,7 +24,7 @@ type InvoiceProductItem struct { Description string `xml:"Description"` - TaxType string `xml:"TaxType"` + TaxType TaxTypeEnum `xml:"TaxType"` SequenceNumber string `xml:"SequenceNumber"` Remark string `xml:"Remark,omitempty"` @@ -40,13 +40,22 @@ type AllowanceProductItem struct { OriginalDescription string `xml:"OriginalDescription"` AllowanceSequenceNumber string `xml:"AllowanceSequenceNumber"` - TaxType string `xml:"TaxType"` + TaxType TaxTypeEnum `xml:"TaxType"` } type F0401ProductItem struct { InvoiceProductItem } +func NewF0401ProductItem(description string) *F0401ProductItem { + return &F0401ProductItem{ + InvoiceProductItem{ + Description: description, + TaxType: TaxTypeTaxable, + }, + } +} + func (item *ProductItem) Validate() error { if item.Quantity == "" { @@ -68,6 +77,8 @@ func (item *ProductItem) Validate() error { } // TODO: check Amount in type of decimal(20,7) + // TODO: check amount = quantity * unit price * (1 + tax rate) + return nil } @@ -89,7 +100,10 @@ func (item *InvoiceProductItem) Validate() error { if item.TaxType == "" { return fmt.Errorf("課稅別 (TaxType) 為必填") } - // TODO: validate TaxType in TaxTypeEnum + err = item.TaxType.Validate() + if err != nil { + return err + } if item.SequenceNumber == "" { return fmt.Errorf("明細排列序號 (SequenceNumber) 為必填") @@ -144,7 +158,10 @@ func (item *AllowanceProductItem) Validate() error { if item.TaxType == "" { return fmt.Errorf("課稅別 (TaxType) 為必填") } - // TODO: validate TaxType in TaxTypeEnum + err = item.TaxType.Validate() + if err != nil { + return err + } return nil } diff --git a/mig/role_description.go b/mig/role_description.go new file mode 100644 index 0000000..c0a4441 --- /dev/null +++ b/mig/role_description.go @@ -0,0 +1,63 @@ +package mig + +import "fmt" + +type RoleDescription struct { + Identifier string `xml:"Identifier"` + Name string `xml:"Name"` + Address string `xml:"Address,omitempty"` + PersonInCharge string `xml:"PersonInCharge,omitempty"` + TelephoneNumber string `xml:"TelephoneNumber,omitempty"` + FacsimileNumber string `xml:"FacsimileNumber,omitempty"` + EmailAddress string `xml:"EmailAddress,omitempty"` + CustomerNumber string `xml:"CustomerNumber,omitempty"` + RoleRemark string `xml:"RoleRemark,omitempty"` +} + +// Validate 檢查賣方資料是否符合規範 +func (item *RoleDescription) Validate() error { + if item.Identifier == "" { + return fmt.Errorf("識別碼 (Identifier) 為必填") + } + // TODO: validate Identifier in type of BAN + + if item.Name == "" { + return fmt.Errorf("名稱 (Name) 為必填") + } + if len(item.Name) < 1 { + return fmt.Errorf("名稱 (Name) 長度不得小於1個字元") + } + if len(item.Name) > 60 { + return fmt.Errorf("名稱 (Name) 長度不得大於60個字元") + } + + if len(item.Address) > 100 { + return fmt.Errorf("地址 (Address) 長度不得大於100") + } + + if len(item.PersonInCharge) > 30 { + return fmt.Errorf("負責人姓名 (PersonInCharge) 長度不得大於30個字元") + } + + if len(item.TelephoneNumber) > 26 { + return fmt.Errorf("電話號碼 (TelephoneNumber) 長度不得大於26個字元") + } + + if len(item.FacsimileNumber) > 26 { + return fmt.Errorf("傳真號碼 (FacsimileNumber) 長度不得大於26個字元") + } + + if len(item.EmailAddress) > 400 { + return fmt.Errorf("電子郵件地址 (EmailAddress) 長度不得大於400個字元") + } + + if len(item.CustomerNumber) > 20 { + return fmt.Errorf("客戶編號 (CustomerNumber) 長度不得大於20個字元") + } + + if len(item.RoleRemark) > 40 { + return fmt.Errorf("營業人角色註記 (RoleRemark) 長度不得大於40個字元") + } + + return nil +} diff --git a/mig/seller.go b/mig/seller.go index 953f269..cb52ab9 100644 --- a/mig/seller.go +++ b/mig/seller.go @@ -2,60 +2,22 @@ package mig import "fmt" -func NewSeller() *RoleDescription { - return &RoleDescription{} +type Seller struct { + RoleDescription } -// Validate 檢查賣方資料是否符合規範 -func (seller *RoleDescription) Validate() error { - if seller.Identifier == "" { - return fmt.Errorf("賣方識別碼(統一編號)為必填") - } - if len(seller.Identifier) > 10 { - // 通常統一編號長度為8個字元,但是規範最大值為10。 - return fmt.Errorf("賣方識別碼(統一編號)長度不得大於10個字元") - } +func NewSeller() *Seller { + return &Seller{} +} - if seller.Name == "" { - return fmt.Errorf("賣方營業人名稱為必填") - } - if len(seller.Name) < 1 { - return fmt.Errorf("賣方營業人名稱長度不得小於1個字元") - } - if len(seller.Name) > 60 { - return fmt.Errorf("賣方營業人名稱長度不得大於60個字元") +func (seller *Seller) Validate() error { + err := seller.RoleDescription.Validate() + if err != nil { + return err } if seller.Address == "" { - return fmt.Errorf("賣方地址欄位為必填") - } - if len(seller.Address) > 100 { - return fmt.Errorf("賣方營業地址長度不得大於100") - } - - if len(seller.PersonInCharge) > 30 { - return fmt.Errorf("賣方負責人姓名長度不得大於30個字元") - } - - if len(seller.TelephoneNumber) > 26 { - return fmt.Errorf("賣方電話號碼長度不得大於26個字元") + return fmt.Errorf("賣方地址欄位 (Address) 為必填") } - - if len(seller.FacsimileNumber) > 26 { - return fmt.Errorf("賣方傳真號碼長度不得大於26個字元") - } - - if len(seller.EmailAddress) > 400 { - return fmt.Errorf("賣方電子郵件地址長度不得大於400個字元") - } - - if len(seller.CustomerNumber) > 20 { - return fmt.Errorf("賣方客戶編號長度不得大於20個字元") - } - - if len(seller.RoleRemark) > 40 { - return fmt.Errorf("賣方營業人角色註記長度不得大於40個字元") - } - return nil } diff --git a/mig/tax_type.go b/mig/tax_type.go new file mode 100644 index 0000000..dc712cd --- /dev/null +++ b/mig/tax_type.go @@ -0,0 +1,26 @@ +package mig + +import "fmt" + +type TaxTypeEnum string + +const ( + // 應稅 + TaxTypeTaxable TaxTypeEnum = "1" + // 零稅率 + TaxTypeZeroRated TaxTypeEnum = "2" + // 免稅 + TaxTypeFreeTax TaxTypeEnum = "3" + // 應稅(特種稅率) + TaxTypeTaxableSpecial TaxTypeEnum = "4" + // 混合應稅與免稅或零稅率 (限訊息 F0401 使用) + TaxTypeMixed TaxTypeEnum = "9" +) + +func (t TaxTypeEnum) Validate() error { + switch t { + case TaxTypeTaxable, TaxTypeZeroRated, TaxTypeFreeTax, TaxTypeTaxableSpecial, TaxTypeMixed: + return nil + } + return fmt.Errorf("課稅別 (TaxType) 欄位格式錯誤") +}