diff --git a/builders.go b/builders.go index b55410f..272ae48 100644 --- a/builders.go +++ b/builders.go @@ -76,8 +76,13 @@ func (b *InvoiceLineAllowanceChargeBuilder) WithAllowanceChargeReason(allowanceC return b } -func (b *InvoiceLineAllowanceChargeBuilder) Build() (allowanceCharge InvoiceLineAllowanceCharge, ok bool) { - if !b.amount.IsInitialized() || b.currencyID == "" { +func (b InvoiceLineAllowanceChargeBuilder) Build() (allowanceCharge InvoiceLineAllowanceCharge, err error) { + if !b.amount.IsInitialized() { + err = NewBuilderErrorf("%v: amount not set", typeName(b)) + return + } + if b.currencyID == "" { + err = NewBuilderErrorf("%v: currency not set", typeName(b)) return } allowanceCharge.ChargeIndicator = b.chargeIndicator @@ -97,7 +102,6 @@ func (b *InvoiceLineAllowanceChargeBuilder) Build() (allowanceCharge InvoiceLine if b.allowanceChargeReason != nil { allowanceCharge.AllowanceChargeReason = *b.allowanceChargeReason } - ok = true return } @@ -117,7 +121,13 @@ type InvoiceLineBuilder struct { invoicePeriod *InvoiceLinePeriod allowancesCharges []InvoiceLineAllowanceCharge - item InvoiceLineItem + + itemName string + itemDescription string + itemSellerID *string + itemStandardItemIdentification *ItemStandardIdentificationCode + itemCommodityClassification *ItemCommodityClassification + itemTaxCategory InvoiceLineTaxCategory } // NewInvoiceLineBuilder creates a new InvoiceLineBuilder @@ -180,17 +190,63 @@ func (b *InvoiceLineBuilder) AppendAllowanceCharge(allowanceCharge InvoiceLineAl return b.WithAllowancesCharges(append(b.allowancesCharges, allowanceCharge)) } -func (b *InvoiceLineBuilder) WithItem(item InvoiceLineItem) *InvoiceLineBuilder { - b.item = item +func (b *InvoiceLineBuilder) WithItemName(name string) *InvoiceLineBuilder { + b.itemName = name + return b +} + +func (b *InvoiceLineBuilder) WithItemDescription(description string) *InvoiceLineBuilder { + b.itemDescription = description + return b +} + +func (b *InvoiceLineBuilder) WithItemSellerID(id string) *InvoiceLineBuilder { + b.itemSellerID = &id + return b +} + +func (b *InvoiceLineBuilder) WithItemStandardItemIdentification(identification ItemStandardIdentificationCode) *InvoiceLineBuilder { + b.itemStandardItemIdentification = &identification + return b +} + +func (b *InvoiceLineBuilder) WithItemCommodityClassification(classification ItemCommodityClassification) *InvoiceLineBuilder { + b.itemCommodityClassification = &classification return b } -func (b *InvoiceLineBuilder) Build() (line InvoiceLine, ok bool) { - if b.id == "" || b.currencyID == "" || - !b.invoicedQuantity.IsInitialized() || - b.unitCode == "" || !b.grossPriceAmount.IsInitialized() || - b.item.Name == "" || b.item.TaxCategory.ID == "" || - b.item.TaxCategory.TaxScheme.ID == "" { +func (b *InvoiceLineBuilder) WithItemTaxCategory(taxCategory InvoiceLineTaxCategory) *InvoiceLineBuilder { + b.itemTaxCategory = taxCategory + return b +} + +func (b InvoiceLineBuilder) Build() (line InvoiceLine, err error) { + if b.id == "" { + err = NewBuilderErrorf("%v: id not set", typeName(b)) + return + } + if b.currencyID == "" { + err = NewBuilderErrorf("%v: id currency id not set", typeName(b)) + return + } + if !b.invoicedQuantity.IsInitialized() { + err = NewBuilderErrorf("%v: invoiced quantity not set", typeName(b)) + return + } + if b.unitCode == "" { + err = NewBuilderErrorf("%v: unit code not set", typeName(b)) + return + } + if !b.grossPriceAmount.IsInitialized() { + err = NewBuilderErrorf("%v: gross price amount not set", typeName(b)) + return + } + if b.itemName == "" { + err = NewBuilderErrorf("%v: item name not set", typeName(b)) + return + } + if b.itemTaxCategory.ID == "" || b.itemTaxCategory.TaxScheme.ID == "" { + err = NewBuilderErrorf("%v: item tax category not set", typeName(b)) return } @@ -231,7 +287,16 @@ func (b *InvoiceLineBuilder) Build() (line InvoiceLine, ok bool) { UnitCode: b.unitCode, } } - line.Item = b.item + + line.Item.Name = b.itemName + line.Item.Description = b.itemDescription + if b.itemSellerID != nil { + line.Item.SellerItemID = NewIDNode(*b.itemSellerID) + } + line.Item.StandardItemIdentification = b.itemStandardItemIdentification + line.Item.CommodityClassification = b.itemCommodityClassification + line.Item.TaxCategory = b.itemTaxCategory + line.AllowanceCharges = b.allowancesCharges line.InvoicePeriod = b.invoicePeriod @@ -243,7 +308,8 @@ func (b *InvoiceLineBuilder) Build() (line InvoiceLine, ok bool) { baseQuantity = *b.baseQuantity } if baseQuantity.IsZero() { - return line, false + err = NewBuilderErrorf("%v: base quantity cannot be zero", typeName(b)) + return } netAmount := b.invoicedQuantity.Mul(netPriceAmount).Div(baseQuantity) for _, charge := range line.AllowanceCharges { @@ -258,7 +324,6 @@ func (b *InvoiceLineBuilder) Build() (line InvoiceLine, ok bool) { Amount: netAmount.AsAmount(), CurrencyID: b.currencyID, } - ok = true return } @@ -330,9 +395,17 @@ func (b *InvoiceDocumentAllowanceChargeBuilder) WithAllowanceChargeReason(allowa return b } -func (b *InvoiceDocumentAllowanceChargeBuilder) Build() (allowanceCharge InvoiceDocumentAllowanceCharge, ok bool) { - if !b.amount.IsInitialized() || b.currencyID == "" || - b.taxCategory.ID == "" || b.taxCategory.TaxScheme.ID == "" { +func (b InvoiceDocumentAllowanceChargeBuilder) Build() (allowanceCharge InvoiceDocumentAllowanceCharge, err error) { + if !b.amount.IsInitialized() { + err = NewBuilderErrorf("%v: amount not set", typeName(b)) + return + } + if b.currencyID == "" { + err = NewBuilderErrorf("%v: current id not set", typeName(b)) + return + } + if b.taxCategory.ID == "" || b.taxCategory.TaxScheme.ID == "" { + err = NewBuilderErrorf("%v: item tax category not set", typeName(b)) return } allowanceCharge.ChargeIndicator = b.chargeIndicator @@ -353,7 +426,6 @@ func (b *InvoiceDocumentAllowanceChargeBuilder) Build() (allowanceCharge Invoice if b.allowanceChargeReason != nil { allowanceCharge.AllowanceChargeReason = *b.allowanceChargeReason } - ok = true return } @@ -456,6 +528,11 @@ func (b *InvoiceBuilder) WithInvoiceLines(invoiceLines []InvoiceLine) *InvoiceBu return b } +func (b *InvoiceBuilder) Append(lines ...InvoiceLine) *InvoiceBuilder { + b.invoiceLines = append(b.invoiceLines, lines...) + return b +} + func (b *InvoiceBuilder) AddTaxExemptionReason(taxCategoryCode TaxCategoryCodeType, reason string, exemptionCode TaxExemptionReasonCodeType) *InvoiceBuilder { if b.taxExeptionReasons == nil { b.taxExeptionReasons = make(map[TaxCategoryCodeType]taxExemptionReason) @@ -467,10 +544,21 @@ func (b *InvoiceBuilder) AddTaxExemptionReason(taxCategoryCode TaxCategoryCodeTy return b } -func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { - if b.id == "" || !b.issueDate.IsInitialized() || - b.documentCurrencyID == "" || - (b.taxCurrencyID != "" && b.taxCurrencyID != b.documentCurrencyID && !b.taxCurrencyExchangeRate.IsInitialized()) { +func (b InvoiceBuilder) Build() (retInvoice Invoice, err error) { + if b.id == "" { + err = NewBuilderErrorf("%v: id not set", typeName(b)) + return + } + if !b.issueDate.IsInitialized() { + err = NewBuilderErrorf("%v: issue date not set", typeName(b)) + return + } + if b.documentCurrencyID == "" { + err = NewBuilderErrorf("%v: document currency id not set", typeName(b)) + return + } + if b.taxCurrencyID != "" && b.taxCurrencyID != b.documentCurrencyID && !b.taxCurrencyExchangeRate.IsInitialized() { + err = NewBuilderErrorf("%v: document to tax currency exchange rate not set", typeName(b)) return } @@ -480,6 +568,8 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { } var invoice Invoice + invoice.Prefill() + invoice.ID = b.id invoice.IssueDate = b.issueDate invoice.DueDate = b.dueDate @@ -518,20 +608,22 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { payableRoundingAmount = Zero payableAmount = Zero ) - taxCategoryMap := make(taxCategoryMap) - for _, line := range invoice.InvoiceLines { + taxCategoryMap := make(taxCategoryMap) + for i, line := range invoice.InvoiceLines { if line.LineExtensionAmount.CurrencyID != invoice.DocumentCurrencyCode { + err = NewBuilderErrorf("%v: invoice line %d: invalid currency id", typeName(b), i) return } lineAmount := line.LineExtensionAmount.Amount lineExtensionAmount = lineExtensionAmount.Add(lineAmount) if !taxCategoryMap.addLineTaxCategory(line.Item.TaxCategory, lineAmount) { + err = NewBuilderErrorf("%v: invoice line %d: invalid tax category", typeName(b), i) return } } - for _, allowanceCharge := range invoice.AllowanceCharges { + for i, allowanceCharge := range invoice.AllowanceCharges { var amount Decimal if allowanceCharge.ChargeIndicator { amount = allowanceCharge.Amount.Amount @@ -541,6 +633,7 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { allowanceTotalAmount = allowanceTotalAmount.Add(allowanceCharge.Amount.Amount) } if !taxCategoryMap.addDocumentTaxCategory(allowanceCharge.TaxCategory, amount) { + err = NewBuilderErrorf("%v: invoice allowance/charge %d: invalid tax category", typeName(b), i) return } } @@ -569,6 +662,8 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { if subtotal.TaxCategory.ID.TaxRateExempted() && subtotal.TaxCategory.ID.ExemptionReasonRequired() { if reason, rok := b.taxExeptionReasons[subtotal.TaxCategory.ID]; !rok { + err = NewBuilderErrorf("%v: tax category %s/%s: no exemption reason", + typeName(b), subtotal.TaxCategory.ID, subtotal.TaxCategory.Percent.String()) return } else { subtotal.TaxCategory.TaxExemptionReason = reason.reason @@ -614,14 +709,6 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { Amount: chargeTotalAmount, CurrencyID: b.documentCurrencyID, } - invoice.LegalMonetaryTotal.PrepaidAmount = &AmountWithCurrency{ - Amount: prepaidAmount, - CurrencyID: b.documentCurrencyID, - } - invoice.LegalMonetaryTotal.PayableRoundingAmount = &AmountWithCurrency{ - Amount: payableRoundingAmount, - CurrencyID: b.documentCurrencyID, - } invoice.LegalMonetaryTotal.TaxExclusiveAmount = AmountWithCurrency{ Amount: taxExclusiveAmount, CurrencyID: b.documentCurrencyID, @@ -630,13 +717,24 @@ func (b *InvoiceBuilder) Build() (retInvoice Invoice, ok bool) { Amount: taxInclusiveAmount, CurrencyID: b.documentCurrencyID, } + if !prepaidAmount.IsZero() { + invoice.LegalMonetaryTotal.PrepaidAmount = &AmountWithCurrency{ + Amount: prepaidAmount, + CurrencyID: b.documentCurrencyID, + } + } + if !payableRoundingAmount.IsZero() { + invoice.LegalMonetaryTotal.PayableRoundingAmount = &AmountWithCurrency{ + Amount: payableRoundingAmount, + CurrencyID: b.documentCurrencyID, + } + } invoice.LegalMonetaryTotal.PayableAmount = AmountWithCurrency{ Amount: payableAmount, CurrencyID: b.documentCurrencyID, } - invoice.Prefill() - retInvoice, ok = invoice, true + retInvoice = invoice return } diff --git a/builders_test.go b/builders_test.go index 3066ce9..b8e76a2 100644 --- a/builders_test.go +++ b/builders_test.go @@ -119,8 +119,8 @@ func TestInvoiceLineBuilder(t *testing.T) { { b := NewInvoiceLineBuilder("1", CurrencyRON) - _, ok := b.Build() - assert.False(ok, "should not build if required fields are missing") + _, err := b.Build() + assert.Error(err, "should not build if required fields are missing") } type lineTest struct { ID string @@ -274,10 +274,8 @@ func TestInvoiceLineBuilder(t *testing.T) { b := NewInvoiceLineBuilder(t.ID, t.CurrencyID). WithUnitCode(t.UnitCode).WithInvoicedQuantity(t.Quantity). WithGrossPriceAmount(t.GrossPrice). - WithItem(InvoiceLineItem{ - Name: t.ItemName, - TaxCategory: t.TaxCategory, - }) + WithItemName(t.ItemName). + WithItemTaxCategory(t.TaxCategory) if !t.BaseQuantity.IsZero() { b.WithBaseQuantity(t.BaseQuantity) } @@ -286,28 +284,30 @@ func TestInvoiceLineBuilder(t *testing.T) { } for _, allowance := range t.Allowances { if !allowance.IsZero() { - lineAllowance, ok := NewInvoiceLineAllowanceBuilder(t.CurrencyID, allowance).Build() - if assert.True(ok) { + lineAllowance, err := NewInvoiceLineAllowanceBuilder(t.CurrencyID, allowance).Build() + if assert.NoError(err) { b.AppendAllowanceCharge(lineAllowance) } } } for _, charge := range t.Charges { if !charge.IsZero() { - lineCharge, ok := NewInvoiceLineChargeBuilder(t.CurrencyID, charge).Build() - if assert.True(ok) { + lineCharge, err := NewInvoiceLineChargeBuilder(t.CurrencyID, charge).Build() + if assert.NoError(err) { b.AppendAllowanceCharge(lineCharge) } } } - line, ok := b.Build() - if assert.True(ok) { + line, err := b.Build() + if assert.NoError(err) { assert.Equal(a(t.ExpectedLineAmount), a(line.LineExtensionAmount.Amount)) assert.Equal(t.ID, line.ID) assert.Equal(d(t.Quantity), d(line.InvoicedQuantity.Quantity)) assert.Equal(t.ItemName, line.Item.Name) + assert.Equal(t.TaxCategory, line.Item.TaxCategory) + // TODO: compare all fields } } @@ -325,8 +325,8 @@ func TestInvoiceBuilder(t *testing.T) { { b := NewInvoiceBuilder("1") - _, ok := b.Build() - assert.False(ok, "should not build if required fields are missing") + _, err := b.Build() + assert.Error(err, "should not build if required fields are missing") } { // A.1.6 Exemplul 5 (Linie a facturii negativă) @@ -334,39 +334,33 @@ func TestInvoiceBuilder(t *testing.T) { var lines []InvoiceLine - line1, ok := NewInvoiceLineBuilder("1", documentCurrencyID). + standardTaxCategory := InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATStandardRate, + Percent: D(25), + } + + line1, err := NewInvoiceLineBuilder("1", documentCurrencyID). WithUnitCode("XBX"). WithInvoicedQuantity(D(25)). WithGrossPriceAmount(D(9.5)). WithPriceDeduction(D(1)). - WithItem(InvoiceLineItem{ - Name: "Stilouri", - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATStandardRate, - Percent: D(25), - }, - }). + WithItemName("Stilouri"). + WithItemTaxCategory(standardTaxCategory). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line1) } - line2, ok := NewInvoiceLineBuilder("2", documentCurrencyID). + line2, err := NewInvoiceLineBuilder("2", documentCurrencyID). WithUnitCode("XBX"). WithInvoicedQuantity(D(-10)). WithGrossPriceAmount(D(9.5)). WithPriceDeduction(D(1)). - WithItem(InvoiceLineItem{ - Name: "Stilouri", - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATStandardRate, - Percent: D(25), - }, - }). + WithItemName("Stilouri"). + WithItemTaxCategory(standardTaxCategory). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line2) } @@ -379,8 +373,8 @@ func TestInvoiceBuilder(t *testing.T) { WithCustomer(getInvoiceCustomerParty()). WithInvoiceLines(lines) - invoice, ok := invoiceBuilder.Build() - if assert.True(ok) { + invoice, err := invoiceBuilder.Build() + if assert.NoError(err) { // Invoice lines if assert.Equal(2, len(invoice.InvoiceLines), "should have correct number of lines") { line1 := invoice.InvoiceLines[0] @@ -419,103 +413,93 @@ func TestInvoiceBuilder(t *testing.T) { } { // A.1.8 Exemplul 7 (Cota normală de TVA cu linii scutite de TVA) - buildInvoice := func(documentCurrencyID CurrencyCodeType) (Invoice, bool) { + buildInvoice := func(documentCurrencyID CurrencyCodeType) (Invoice, error) { var lines []InvoiceLine - line1, ok := NewInvoiceLineBuilder("1", documentCurrencyID). + line1, err := NewInvoiceLineBuilder("1", documentCurrencyID). WithUnitCode("H87"). WithInvoicedQuantity(D(5)). WithGrossPriceAmount(D(25.0)). - WithItem(InvoiceLineItem{ - Name: Transliterate("Cerneală pentru imprimantă"), - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATStandardRate, - Percent: D(25), - }, + WithItemName(Transliterate("Cerneală pentru imprimantă")). + WithItemTaxCategory(InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATStandardRate, + Percent: D(25), }). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line1) } - line2, ok := NewInvoiceLineBuilder("2", documentCurrencyID). + line2, err := NewInvoiceLineBuilder("2", documentCurrencyID). WithUnitCode("H87"). WithInvoicedQuantity(D(1)). WithGrossPriceAmount(D(24.0)). - WithItem(InvoiceLineItem{ - Name: Transliterate("Imprimare afiș"), - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATStandardRate, - Percent: D(10), - }, + WithItemName(Transliterate("Imprimare afiș")). + WithItemTaxCategory(InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATStandardRate, + Percent: D(10), }). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line2) } - line3, ok := NewInvoiceLineBuilder("3", documentCurrencyID). + line3, err := NewInvoiceLineBuilder("3", documentCurrencyID). WithUnitCode("H87"). WithInvoicedQuantity(D(1)). WithGrossPriceAmount(D(136.0)). - WithItem(InvoiceLineItem{ - Name: Transliterate("Scaun de birou"), - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATStandardRate, - Percent: D(25), - }, + WithItemName(Transliterate("Scaun de birou")). + WithItemTaxCategory(InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATStandardRate, + Percent: D(25), }). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line3) } - line4, ok := NewInvoiceLineBuilder("4", documentCurrencyID). + line4, err := NewInvoiceLineBuilder("4", documentCurrencyID). WithUnitCode("H87"). WithInvoicedQuantity(D(1)). WithGrossPriceAmount(D(95.0)). - WithItem(InvoiceLineItem{ - Name: Transliterate("Tastatură fără fir"), - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATExempt, - }, + WithItemName(Transliterate("Tastatură fără fir")). + WithItemTaxCategory(InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATExempt, }). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line4) } - line5, ok := NewInvoiceLineBuilder("5", documentCurrencyID). + line5, err := NewInvoiceLineBuilder("5", documentCurrencyID). WithUnitCode("H87"). WithInvoicedQuantity(D(1)). WithGrossPriceAmount(D(53.0)). - WithItem(InvoiceLineItem{ - Name: Transliterate("Cablu de adaptare"), - TaxCategory: InvoiceLineTaxCategory{ - TaxScheme: TaxSchemeVAT, - ID: TaxCategoryVATExempt, - }, + WithItemName(Transliterate("Cablu de adaptare")). + WithItemTaxCategory(InvoiceLineTaxCategory{ + TaxScheme: TaxSchemeVAT, + ID: TaxCategoryVATExempt, }). Build() - if assert.True(ok) { + if assert.NoError(err) { lines = append(lines, line5) } invoiceBuilder := NewInvoiceBuilder("test.example.07"). WithIssueDate(MakeDate(2024, 3, 1)). WithDueDate(MakeDate(2024, 4, 1)). - WithInvoiceTypeCode(InvoiceTypeCommercialInvoice). + WithInvoiceTypeCode(InvoiceTypeSelfBilledInvoice). WithDocumentCurrencyCode(documentCurrencyID). WithSupplier(getInvoiceSupplierParty()). WithCustomer(getInvoiceCustomerParty()). WithInvoiceLines(lines). AddTaxExemptionReason(TaxCategoryVATExempt, "MOTIVUL A", "") - documentAllowance, ok := NewInvoiceDocumentAllowanceBuilder( + documentAllowance, err := NewInvoiceDocumentAllowanceBuilder( documentCurrencyID, D(15), InvoiceTaxCategory{ @@ -524,11 +508,11 @@ func TestInvoiceBuilder(t *testing.T) { Percent: D(25), }, ).WithAllowanceChargeReason("Motivul C").Build() - if assert.True(ok) { + if assert.NoError(err) { invoiceBuilder.AppendAllowanceCharge(documentAllowance) } - documentCharge, ok := NewInvoiceDocumentChargeBuilder( + documentCharge, err := NewInvoiceDocumentChargeBuilder( documentCurrencyID, D(35), InvoiceTaxCategory{ @@ -537,7 +521,7 @@ func TestInvoiceBuilder(t *testing.T) { Percent: D(25), }, ).WithAllowanceChargeReason("Motivul B").Build() - if assert.True(ok) { + if assert.NoError(err) { invoiceBuilder.AppendAllowanceCharge(documentCharge) } @@ -545,14 +529,13 @@ func TestInvoiceBuilder(t *testing.T) { invoiceBuilder.WithTaxCurrencyCode(CurrencyRON) invoiceBuilder.WithDocumentToTaxCurrencyExchangeRate(D(4.9691)) } - return invoiceBuilder.Build() } { // BT-5 is RON - invoice, ok := buildInvoice(CurrencyRON) - if assert.True(ok) { + invoice, err := buildInvoice(CurrencyRON) + if assert.NoError(err) { // Invoice lines if assert.Equal(5, len(invoice.InvoiceLines)) { line1 := invoice.InvoiceLines[0] @@ -609,8 +592,8 @@ func TestInvoiceBuilder(t *testing.T) { { // BT-5 is EUR, BT-6 is RON documentCurrencyID := CurrencyEUR - invoice, ok := buildInvoice(documentCurrencyID) - if assert.True(ok) { + invoice, err := buildInvoice(documentCurrencyID) + if assert.NoError(err) { // Invoice lines if assert.Equal(5, len(invoice.InvoiceLines)) { line1 := invoice.InvoiceLines[0] diff --git a/errors.go b/errors.go index 3c3f881..5782ccd 100644 --- a/errors.go +++ b/errors.go @@ -73,3 +73,29 @@ func (r *ErrorResponse) Error() string { } return m } + +// BuilderError is an error returned by the builders. +type BuilderError struct { + error + Term *string +} + +func NewBuilderErrorf(format string, a ...any) *BuilderError { + return &BuilderError{ + error: fmt.Errorf(format, a...), + } +} + +func NewBuilderTermErrorf(term string, format string, a ...any) *BuilderError { + return &BuilderError{ + error: fmt.Errorf(format, a...), + } +} + +func (e *BuilderError) Error() string { + if e.Term == nil { + return e.error.Error() + } + + return fmt.Sprintf("term: %s, error: %s", *e.Term, e.error.Error()) +} diff --git a/helpers.go b/helpers.go index d4fb355..714c1a7 100644 --- a/helpers.go +++ b/helpers.go @@ -21,6 +21,7 @@ import ( "mime" "net/http" "net/url" + "reflect" "regexp" "strconv" @@ -155,3 +156,7 @@ func matchFirstSubmatch(input string, re *regexp.Regexp) (string, bool) { func ptrfyString(s string) *string { return &s } + +func typeName(v any) string { + return reflect.TypeOf(v).Name() +} diff --git a/invoice.go b/invoice.go index ede5c75..9262e48 100644 --- a/invoice.go +++ b/invoice.go @@ -1059,7 +1059,7 @@ type InvoiceLineItem struct { // Cardinality: 0..1 Description string `xml:"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2 Description,omitempty"` // ID: BT-155 - // Term: BT-155 + // Term: Identificatorul Vânzătorului articolului // Cardinality: 0..1 SellerItemID *IDNode `xml:"urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2 SellersItemIdentification,omitempty"` // ID: BT-157/BT-157-1 @@ -1194,6 +1194,14 @@ type IDNode struct { ID string `xml:"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2 ID"` } +func MakeIDNode(id string) IDNode { + return IDNode{ID: id} +} + +func NewIDNode(id string) *IDNode { + return &IDNode{ID: id} +} + type TaxScheme struct { ID TaxSchemeIDType `xml:"urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2 ID"` }