diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 9524729..1f6bae7 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -23,5 +23,5 @@ jobs: run: dotnet restore - name: Build run: dotnet build --no-restore - #- name: Test - # run: dotnet test --no-build --verbosity normal + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/FacturaE.sln b/FacturaE.sln index 434c9b4..e84039a 100644 --- a/FacturaE.sln +++ b/FacturaE.sln @@ -7,6 +7,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{88086700-F1E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FacturaE", "src\facturae\FacturaE.csproj", "{E31A3B5E-FEA2-4FC0-80C1-415C91A635AF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C24D7763-A8A5-4799-8D1A-0CA4E4E0DB67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "facturae.tests", "tests\facturae.tests\facturae.tests.csproj", "{FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,8 +24,13 @@ Global {E31A3B5E-FEA2-4FC0-80C1-415C91A635AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {E31A3B5E-FEA2-4FC0-80C1-415C91A635AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {E31A3B5E-FEA2-4FC0-80C1-415C91A635AF}.Release|Any CPU.Build.0 = Release|Any CPU + {FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {E31A3B5E-FEA2-4FC0-80C1-415C91A635AF} = {88086700-F1EB-4E51-B9E1-4F4D06B94876} + {FCCCD1AE-EFF6-4DD0-BFD2-DD0455770783} = {C24D7763-A8A5-4799-8D1A-0CA4E4E0DB67} EndGlobalSection EndGlobal diff --git a/src/facturae/DataType/DoubleFourDecimalType.cs b/src/facturae/DataType/DoubleFourDecimalType.cs index 1bc233c..67ab324 100644 --- a/src/facturae/DataType/DoubleFourDecimalType.cs +++ b/src/facturae/DataType/DoubleFourDecimalType.cs @@ -40,6 +40,16 @@ public static DoubleFourDecimalType Parse(string value) return new DoubleFourDecimalType(decimal.Parse(value)); } + public static bool operator ==(DoubleFourDecimalType left, decimal dec) + { + return left._value == dec; + } + + public static bool operator !=(DoubleFourDecimalType left, decimal dec) + { + return left._value != dec; + } + public static bool operator ==(DoubleFourDecimalType left, DoubleFourDecimalType right) { return left._value == right._value; @@ -109,7 +119,19 @@ public override readonly int GetHashCode() public override readonly bool Equals(object obj) { - return obj is not null and DoubleFourDecimalType value && value == this; + if (obj is null) + { + return false; + } + else if (obj is decimal dec) + { + return _value == dec; + } + else if (obj is DoubleFourDecimalType dfdt) + { + return dfdt == this; + } + return false; } public override readonly string ToString() @@ -124,6 +146,27 @@ public readonly string ToString(string format) public readonly int CompareTo(object obj) { + if (obj is decimal dec) + { + return _value.CompareTo(dec); + } + else if (obj is DoubleFourDecimalType dfdt) + { + return _value.CompareTo((decimal)dfdt); + } + else if (obj is DoubleSixDecimalType dsdt) + { + return _value.CompareTo((decimal)dsdt); + } + else if (obj is DoubleTwoDecimalType dtdt) + { + return _value.CompareTo((decimal)dtdt); + } + else if (obj is DoubleUpToEightDecimalType dedt) + { + return _value.CompareTo((decimal)dedt); + } + return _value.CompareTo(obj); } diff --git a/src/facturae/DataType/DoubleSixDecimalType.cs b/src/facturae/DataType/DoubleSixDecimalType.cs index b59997d..28adafa 100644 --- a/src/facturae/DataType/DoubleSixDecimalType.cs +++ b/src/facturae/DataType/DoubleSixDecimalType.cs @@ -40,6 +40,16 @@ public static DoubleSixDecimalType Parse(string value) return new DoubleSixDecimalType(decimal.Parse(value)); } + public static bool operator ==(DoubleSixDecimalType left, decimal dec) + { + return left._value == dec; + } + + public static bool operator !=(DoubleSixDecimalType left, decimal dec) + { + return left._value != dec; + } + public static bool operator ==(DoubleSixDecimalType left, DoubleSixDecimalType right) { return left._value == right._value; @@ -109,7 +119,19 @@ public override readonly int GetHashCode() public override readonly bool Equals(object obj) { - return obj is not null and DoubleSixDecimalType dvalue && dvalue == this; + if (obj is null) + { + return false; + } + else if (obj is decimal dec) + { + return _value == dec; + } + else if (obj is DoubleSixDecimalType dsdt) + { + return dsdt == this; + } + return false; } public override readonly string ToString() @@ -124,6 +146,27 @@ public readonly string ToString(string format) public readonly int CompareTo(object obj) { + if (obj is decimal dec) + { + return _value.CompareTo(dec); + } + else if (obj is DoubleFourDecimalType dfdt) + { + return _value.CompareTo((decimal)dfdt); + } + else if (obj is DoubleSixDecimalType dsdt) + { + return _value.CompareTo((decimal)dsdt); + } + else if (obj is DoubleTwoDecimalType dtdt) + { + return _value.CompareTo((decimal)dtdt); + } + else if (obj is DoubleUpToEightDecimalType dedt) + { + return _value.CompareTo((decimal)dedt); + } + return _value.CompareTo(obj); } diff --git a/src/facturae/DataType/DoubleTwoDecimalType.cs b/src/facturae/DataType/DoubleTwoDecimalType.cs index 67695d1..e5e4825 100644 --- a/src/facturae/DataType/DoubleTwoDecimalType.cs +++ b/src/facturae/DataType/DoubleTwoDecimalType.cs @@ -40,6 +40,16 @@ public static DoubleTwoDecimalType Parse(string value) return new DoubleTwoDecimalType(decimal.Parse(value)); } + public static bool operator ==(DoubleTwoDecimalType left, decimal dec) + { + return left._value == dec; + } + + public static bool operator !=(DoubleTwoDecimalType left, decimal dec) + { + return left._value != dec; + } + public static bool operator ==(DoubleTwoDecimalType left, DoubleTwoDecimalType right) { return left._value == right._value; @@ -109,7 +119,19 @@ public override readonly int GetHashCode() public override readonly bool Equals(object obj) { - return obj is not null && obj is DoubleTwoDecimalType value && value == this; + if (obj is null) + { + return false; + } + else if (obj is decimal dec) + { + return _value == dec; + } + else if (obj is DoubleTwoDecimalType dsdt) + { + return dsdt == this; + } + return false; } public override readonly string ToString() @@ -124,6 +146,27 @@ public readonly string ToString(string format) public readonly int CompareTo(object obj) { + if (obj is decimal dec) + { + return _value.CompareTo(dec); + } + else if (obj is DoubleFourDecimalType dfdt) + { + return _value.CompareTo((decimal)dfdt); + } + else if (obj is DoubleSixDecimalType dsdt) + { + return _value.CompareTo((decimal)dsdt); + } + else if (obj is DoubleTwoDecimalType dtdt) + { + return _value.CompareTo((decimal)dtdt); + } + else if (obj is DoubleUpToEightDecimalType dedt) + { + return _value.CompareTo((decimal)dedt); + } + return _value.CompareTo(obj); } diff --git a/src/facturae/DataType/DoubleUpToEightDecimalType.cs b/src/facturae/DataType/DoubleUpToEightDecimalType.cs index 5afe177..07f9cbb 100644 --- a/src/facturae/DataType/DoubleUpToEightDecimalType.cs +++ b/src/facturae/DataType/DoubleUpToEightDecimalType.cs @@ -40,6 +40,17 @@ public static DoubleUpToEightDecimalType Parse(string value) return new DoubleUpToEightDecimalType(decimal.Parse(value)); } + public static bool operator ==(DoubleUpToEightDecimalType left, decimal dec) + { + return left._value == dec; + } + + + public static bool operator !=(DoubleUpToEightDecimalType left, decimal dec) + { + return left._value != dec; + } + public static bool operator ==(DoubleUpToEightDecimalType left, DoubleUpToEightDecimalType right) { return left._value == right._value; @@ -109,7 +120,19 @@ public override readonly int GetHashCode() public override readonly bool Equals(object obj) { - return obj is not null and DoubleUpToEightDecimalType dvalue && dvalue == this; + if (obj is null) + { + return false; + } + else if (obj is decimal dec) + { + return _value == dec; + } + else if (obj is DoubleUpToEightDecimalType dedt) + { + return dedt == this; + } + return false; } public override readonly string ToString() @@ -124,6 +147,27 @@ public readonly string ToString(string format) public readonly int CompareTo(object obj) { + if (obj is decimal dec) + { + return _value.CompareTo(dec); + } + else if (obj is DoubleFourDecimalType dfdt) + { + return _value.CompareTo((decimal)dfdt); + } + else if (obj is DoubleSixDecimalType dsdt) + { + return _value.CompareTo((decimal)dsdt); + } + else if (obj is DoubleTwoDecimalType dtdt) + { + return _value.CompareTo((decimal)dtdt); + } + else if (obj is DoubleUpToEightDecimalType dedt) + { + return _value.CompareTo((decimal)dedt); + } + return _value.CompareTo(obj); } @@ -217,9 +261,9 @@ readonly ulong IConvertible.ToUInt64(IFormatProvider provider) return Convert.ToUInt64(_value); } - readonly int IComparable.CompareTo(decimal other) + public readonly int CompareTo(decimal other) { - return CompareTo(other); + return _value.CompareTo(other); } public readonly bool Equals(decimal other) diff --git a/src/facturae/FacturaE.csproj b/src/facturae/FacturaE.csproj index 1b22525..43f2582 100644 --- a/src/facturae/FacturaE.csproj +++ b/src/facturae/FacturaE.csproj @@ -1,35 +1,26 @@  - facturae;dotnet8 - https://github.com/carlosga/facturae - https://github.com/carlosga/facturae/blob/master/LICENSE + net8.0 + enable + + true + https://github.com/carlosga/facturae + https://github.com/carlosga/facturae/blob/master/LICENSE electronic invoice signing using pure C# FacturaE + facturae;dotnet8 FacturaE 1.1.0 - - Exe - net8.0 - enable - true - - - - - Always - - - diff --git a/src/facturae/Facturae.Extensions.cs b/src/facturae/Facturae.Extensions.cs index 5ba3142..f5dab59 100644 --- a/src/facturae/Facturae.Extensions.cs +++ b/src/facturae/Facturae.Extensions.cs @@ -101,7 +101,8 @@ public Facturae CalculateTotals() FileHeader.Batch.TotalInvoicesAmount = SumTotalAmounts(); FileHeader.Batch.TotalOutstandingAmount = SumTotalOutstandingAmount(); FileHeader.Batch.TotalExecutableAmount = SumTotalExecutableAmount(); - FileHeader.Batch.BatchIdentifier = firstInvoice.InvoiceHeader.InvoiceNumber + FileHeader.Batch.BatchIdentifier = Parties.SellerParty.TaxIdentification.TaxIdentificationNumber + + firstInvoice.InvoiceHeader.InvoiceNumber + firstInvoice.InvoiceHeader.InvoiceSeriesCode; return this; @@ -178,6 +179,11 @@ public InvoiceType CreateInvoice() public partial class InvoiceType { + public Facturae Root() + { + return Parent; + } + /// /// Set the invoice series code. /// @@ -419,13 +425,15 @@ public InvoiceType SetLanguage(LanguageCodeType language) /// The product code. /// The product description. /// - public InvoiceLineType AddInvoiceItem(string productCode, string productDescription) + public InvoiceLineType AddInvoiceItem(string productCode = null, string productDescription = null) { var item = new InvoiceLineType { Parent = this, ArticleCode = productCode, - ItemDescription = productDescription + ItemDescription = productDescription, + TaxesOutputs = new List(), + TaxesWithheld = new List() }; Items.Add(item); @@ -464,15 +472,15 @@ public Facturae CalculateTotals() EquivalenceSurchargeAmount = new AmountType { TotalAmount = g.Where(gtax => gtax.EquivalenceSurchargeSpecified) - .Sum(gtax => gtax.EquivalenceSurchargeAmount.TotalAmount).Round(), + .Sum(gtax => gtax.EquivalenceSurchargeAmount.TotalAmount).Round(), EquivalentInEuros = g.Where(gtax => gtax.EquivalenceSurchargeSpecified) - .Sum(gtax => gtax.EquivalenceSurchargeAmount.EquivalentInEuros).Round(), + .Sum(gtax => gtax.EquivalenceSurchargeAmount.EquivalentInEuros).Round(), EquivalentInEurosSpecified = true }, TaxTypeCode = g.Key.TaxTypeCode }; - TaxesOutputs = q.OrderBy(x => x.TaxTypeCode).ThenBy(x => x.TaxRate).ThenBy(x => x.EquivalenceSurcharge).ToList(); + TaxesOutputs = [.. q.OrderBy(x => x.TaxTypeCode).ThenBy(x => x.TaxRate).ThenBy(x => x.EquivalenceSurcharge)]; // Taxes Withheld var w = from tax in Items.SelectMany(x => x.TaxesWithheld) @@ -493,7 +501,7 @@ public Facturae CalculateTotals() TaxTypeCode = g.Key.TaxTypeCode }; - TaxesWithheld = w.OrderBy(x => x.TaxTypeCode).ThenBy(x => x.TaxRate).ToList(); + TaxesWithheld = [.. w.OrderBy(x => x.TaxTypeCode).ThenBy(x => x.TaxRate)]; // Invoice totals InvoiceTotals = new InvoiceTotalsType(); @@ -613,14 +621,43 @@ private DoubleUpToEightDecimalType CalculateTaxWithheldTotal() public partial class InvoiceLineType { + public InvoiceLineType SetIssuerContractReference(string reference) + { + IssuerContractReference = reference; + + return this; + } + + public InvoiceLineType SetIssuerContractDate(DateTime? value) + { + IssuerContractDate = value ?? DateTime.Today; + IssuerContractDateSpecified = value.HasValue; + + return this; + } + + public InvoiceLineType SetIssuerTransactionReference(string reference) + { + IssuerTransactionReference = reference; + + return this; + } + public InvoiceLineType SetIssuerTransactionDate(DateTime? value) { - IssuerTransactionDate = value.HasValue ? value.Value : DateTime.Today; + IssuerTransactionDate = value ?? DateTime.Today; IssuerTransactionDateSpecified = value.HasValue; return this; } + public InvoiceLineType SetDescription(string description) + { + ItemDescription = description; + + return this; + } + public InvoiceLineType GiveQuantity(decimal quantity) { Quantity = quantity; @@ -628,6 +665,14 @@ public InvoiceLineType GiveQuantity(decimal quantity) return this; } + public InvoiceLineType GiveUnitOfMeasure(UnitOfMeasureType? measureType) + { + UnitOfMeasure = measureType ?? UnitOfMeasureType.Units; + UnitOfMeasureSpecified = measureType.HasValue; + + return this; + } + public InvoiceLineType GiveUnitPriceWithoutTax(DoubleUpToEightDecimalType price) { UnitPriceWithoutTax = price; @@ -675,8 +720,6 @@ public InvoiceLineType GiveTaxRate(DoubleUpToEightDecimalType taxRate, TaxTypeCo private void AddTaxOutput(DoubleUpToEightDecimalType taxRate, TaxTypeCodeType taxType, DoubleTwoDecimalType? equivalenceSurcharge = null) { - TaxesOutputs ??= new List(1); - var tax = new InvoiceLineTypeTax { TaxTypeCode = taxType, @@ -690,8 +733,6 @@ private void AddTaxOutput(DoubleUpToEightDecimalType taxRate, TaxTypeCodeType ta private void AddTaxWithheld(DoubleUpToEightDecimalType taxRate, TaxTypeCodeType taxType) { - TaxesWithheld ??= new List(1); - var tax = new TaxType { TaxTypeCode = taxType, @@ -729,7 +770,7 @@ public InvoiceType CalculateTotals() // Result: TotalCost - DiscountAmount + ChargeAmount. Up to eight decimal points GrossAmount = TotalCost - totalDiscounts + totalCharges; - TaxesOutputs.ForEach + TaxesOutputs?.ForEach ( tax => { @@ -757,7 +798,7 @@ public InvoiceType CalculateTotals() var discounts = DiscountsAndRebates?.Sum(x => x.DiscountAmount).Round() ?? 0; - TaxesWithheld.ForEach + TaxesWithheld?.ForEach ( tax => { diff --git a/src/facturae/Program.cs b/src/facturae/Program.cs deleted file mode 100644 index 79dc4d1..0000000 --- a/src/facturae/Program.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Carlos Guzmán Álvarez. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Security.Cryptography.X509Certificates; - -namespace FacturaE; - -public class Program -{ - static void Main(string[] args) - { - var certificate = new X509Certificate2(@"Certificates/facturae.p12", "1234"); - - // Create a new facturae invoice & sign it - new Facturae() - .Seller() - .SetIdentification("00001") - .AsResidentInSpain() - .SetIdentificationNumber("35799562Q") - .AsIndividual() - .SetName("JOHN") - .SetFirstSurname("DOE") - .SetAddress("8585 FIRST STREET") - .SetProvince("MADRID") - .SetTown("MADRID") - .SetPostCode("99900") - .SetCountryCode(CountryType.ESP) - .Party() - .Invoice() - .Buyer() - .SetIdentification("00002") - .AsResidentInSpain() - .SetIdentificationNumber("06990097Y") - .AsLegalEntity() - .SetCorporateName("JOHN") - .SetAddress("8585 FIRST STREET") - .SetProvince("MADRID") - .SetTown("MADRID") - .SetPostCode("99900") - .SetCountryCode(CountryType.ESP) - .Party() - .AddAdministrativeCentre() - .SetCentreCode("1") - .SetRoleCodeType("02") - .SetLogicalOperationalPoint("1233") - .SetName("ADMINISTRATION NAME") - .SetAddress("1234 Street") - .SetProvince("MADRID") - .SetTown("MADRID") - .SetPostCode("99900") - .SetCountryCode(CountryType.ESP) - .Party() - .Invoice() - .CreateInvoice() - .SetCurrency(CurrencyCodeType.EUR) - .SetExchangeRate(1, DateTime.Today) - .SetTaxCurrency(CurrencyCodeType.EUR) - .SetLanguage(LanguageCodeType.es) - .SetPlaceOfIssue(string.Empty, "00000") - .IsOriginal() - .IsComplete() - .SetInvoiceSeries("IN") - .SetInvoiceNumber("1000") - .AddInvoiceItem("XX", "XX") - .GiveQuantity(1.0M) - .GiveUnitPriceWithoutTax(100.01M) - .GiveDiscount(10.01M, "Line Discount") - .GiveValueAddedTaxRate(21.0M, 5.20M) - .GiveTaxRate(9.0M, TaxTypeCodeType.PersonalIncomeTax) - .CalculateTotals() - .AddInvoiceItem("XXX", "XXX") - .GiveQuantity(1) - .GiveUnitPriceWithoutTax(100.01M) - .GiveDiscount(10.01M) - .GiveValueAddedTaxRate(21.0M, 5.20M) - .GiveTaxRate(9.0M, TaxTypeCodeType.PersonalIncomeTax) - .CalculateTotals() - .CalculateTotals() - .CalculateTotals() - .Validate() - .Sign(certificate) - .CheckSignature() - .WriteToFile("signed-invoice.xsig"); - } -} diff --git a/src/facturae/RFC2253/ASN1/AsnBitString.cs b/src/facturae/RFC2253/ASN1/AsnBitString.cs index 4c48b48..c977a2b 100644 --- a/src/facturae/RFC2253/ASN1/AsnBitString.cs +++ b/src/facturae/RFC2253/ASN1/AsnBitString.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnBitString : AsnString +internal sealed class AsnBitString : AsnString { private static string Encode(ReadOnlyMemory buffer) { diff --git a/src/facturae/RFC2253/ASN1/AsnBmpString.cs b/src/facturae/RFC2253/ASN1/AsnBmpString.cs index 3ed6f97..e66c83f 100644 --- a/src/facturae/RFC2253/ASN1/AsnBmpString.cs +++ b/src/facturae/RFC2253/ASN1/AsnBmpString.cs @@ -8,7 +8,7 @@ namespace ASN1; /// /// ASN.1 BMP (Basic Multilingual Plane) String /// -public sealed class AsnBmpString : AsnString +internal sealed class AsnBmpString : AsnString { private static readonly Encoding s_encoding1201 = Encoding.GetEncoding(1201); private static readonly Encoding s_encoding1200 = Encoding.GetEncoding(1200); diff --git a/src/facturae/RFC2253/ASN1/AsnClass.cs b/src/facturae/RFC2253/ASN1/AsnClass.cs index 4babf4a..6d55029 100644 --- a/src/facturae/RFC2253/ASN1/AsnClass.cs +++ b/src/facturae/RFC2253/ASN1/AsnClass.cs @@ -3,7 +3,7 @@ namespace ASN1; -public enum AsnClass +internal enum AsnClass { Universal = 1, Application = 2, diff --git a/src/facturae/RFC2253/ASN1/AsnDecoder.cs b/src/facturae/RFC2253/ASN1/AsnDecoder.cs index e54d93f..24d443c 100644 --- a/src/facturae/RFC2253/ASN1/AsnDecoder.cs +++ b/src/facturae/RFC2253/ASN1/AsnDecoder.cs @@ -8,7 +8,7 @@ namespace ASN1; /// http://msdn.microsoft.com/en-us/library/windows/desktop/bb648643%28v=vs.85%29.aspx /// http://msdn.microsoft.com/en-us/library/windows/desktop/bb540809(v=vs.85).aspx /// -public sealed class AsnDecoder +internal sealed class AsnDecoder { private readonly ReadOnlyMemory _memory; private int _position; diff --git a/src/facturae/RFC2253/ASN1/AsnForm.cs b/src/facturae/RFC2253/ASN1/AsnForm.cs index 944671c..e64c91e 100644 --- a/src/facturae/RFC2253/ASN1/AsnForm.cs +++ b/src/facturae/RFC2253/ASN1/AsnForm.cs @@ -3,7 +3,7 @@ namespace ASN1; -public enum AsnForm +internal enum AsnForm { Primitive = 0, Constructed = 1 diff --git a/src/facturae/RFC2253/ASN1/AsnIA5String.cs b/src/facturae/RFC2253/ASN1/AsnIA5String.cs index a960d50..b28df35 100644 --- a/src/facturae/RFC2253/ASN1/AsnIA5String.cs +++ b/src/facturae/RFC2253/ASN1/AsnIA5String.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnIA5String : AsnString +internal sealed class AsnIA5String : AsnString { public AsnIA5String(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer, Encoding.ASCII.GetString(buffer.Span)) diff --git a/src/facturae/RFC2253/ASN1/AsnIdentifier.cs b/src/facturae/RFC2253/ASN1/AsnIdentifier.cs index 8e35ad2..624aa12 100644 --- a/src/facturae/RFC2253/ASN1/AsnIdentifier.cs +++ b/src/facturae/RFC2253/ASN1/AsnIdentifier.cs @@ -23,7 +23,7 @@ namespace ASN1; /// 1 1 1 1 0 (0-30) single octet tag /// 1 1 1 1 1 (> 30) multiple octet tag, more octets follow /// -public struct AsnIdentifier +internal struct AsnIdentifier { public AsnClass Class { diff --git a/src/facturae/RFC2253/ASN1/AsnInteger.cs b/src/facturae/RFC2253/ASN1/AsnInteger.cs index 90493ae..ee22f81 100644 --- a/src/facturae/RFC2253/ASN1/AsnInteger.cs +++ b/src/facturae/RFC2253/ASN1/AsnInteger.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnInteger : AsnValueObject +internal sealed class AsnInteger : AsnValueObject { public AsnInteger(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer) { diff --git a/src/facturae/RFC2253/ASN1/AsnObject.cs b/src/facturae/RFC2253/ASN1/AsnObject.cs index 69409a0..b54389d 100644 --- a/src/facturae/RFC2253/ASN1/AsnObject.cs +++ b/src/facturae/RFC2253/ASN1/AsnObject.cs @@ -3,7 +3,7 @@ namespace ASN1; -public abstract class AsnObject +internal abstract class AsnObject { public AsnIdentifier Id { diff --git a/src/facturae/RFC2253/ASN1/AsnObjectIdentifier.cs b/src/facturae/RFC2253/ASN1/AsnObjectIdentifier.cs index f759203..b7577be 100644 --- a/src/facturae/RFC2253/ASN1/AsnObjectIdentifier.cs +++ b/src/facturae/RFC2253/ASN1/AsnObjectIdentifier.cs @@ -6,7 +6,7 @@ namespace ASN1; -public sealed class AsnObjectIdentifier : AsnValueObject +internal sealed class AsnObjectIdentifier : AsnValueObject { public AsnObjectIdentifier(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer) { diff --git a/src/facturae/RFC2253/ASN1/AsnOctetString.cs b/src/facturae/RFC2253/ASN1/AsnOctetString.cs index e42acd7..3b24be6 100644 --- a/src/facturae/RFC2253/ASN1/AsnOctetString.cs +++ b/src/facturae/RFC2253/ASN1/AsnOctetString.cs @@ -3,7 +3,7 @@ namespace ASN1; -public sealed class AsnOctetString : AsnString +internal sealed class AsnOctetString : AsnString { public AsnOctetString(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer, buffer.Span.ByteArrayToHex(" ")) diff --git a/src/facturae/RFC2253/ASN1/AsnPrintableString.cs b/src/facturae/RFC2253/ASN1/AsnPrintableString.cs index 30bc138..ab750f9 100644 --- a/src/facturae/RFC2253/ASN1/AsnPrintableString.cs +++ b/src/facturae/RFC2253/ASN1/AsnPrintableString.cs @@ -8,7 +8,7 @@ namespace ASN1; /// /// https://github.com/notpeter/openjdk/blob/1917a46616cf12591ee26d38a4adbc5f105f38cb/jdk/test/javax/security/auth/x500/X500Principal/NameFormat.java /// -public sealed class AsnPrintableString : AsnString +internal sealed class AsnPrintableString : AsnString { public AsnPrintableString(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer, Encoding.ASCII.GetString(buffer.Span)) diff --git a/src/facturae/RFC2253/ASN1/AsnSequence.cs b/src/facturae/RFC2253/ASN1/AsnSequence.cs index 5198b3a..e318af9 100644 --- a/src/facturae/RFC2253/ASN1/AsnSequence.cs +++ b/src/facturae/RFC2253/ASN1/AsnSequence.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnSequence : AsnObject, IEnumerable +internal sealed class AsnSequence : AsnObject, IEnumerable { private readonly List _objects = []; diff --git a/src/facturae/RFC2253/ASN1/AsnSet.cs b/src/facturae/RFC2253/ASN1/AsnSet.cs index 48fd613..3209e77 100644 --- a/src/facturae/RFC2253/ASN1/AsnSet.cs +++ b/src/facturae/RFC2253/ASN1/AsnSet.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnSet : AsnObject, IEnumerable +internal sealed class AsnSet : AsnObject, IEnumerable { private readonly List _objects = []; diff --git a/src/facturae/RFC2253/ASN1/AsnString.cs b/src/facturae/RFC2253/ASN1/AsnString.cs index d5cf36d..18d5402 100644 --- a/src/facturae/RFC2253/ASN1/AsnString.cs +++ b/src/facturae/RFC2253/ASN1/AsnString.cs @@ -3,7 +3,7 @@ namespace ASN1; -public abstract class AsnString : AsnValueObject +internal abstract class AsnString : AsnValueObject { protected AsnString(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer) { diff --git a/src/facturae/RFC2253/ASN1/AsnT61String.cs b/src/facturae/RFC2253/ASN1/AsnT61String.cs index 6ea80e4..a411322 100644 --- a/src/facturae/RFC2253/ASN1/AsnT61String.cs +++ b/src/facturae/RFC2253/ASN1/AsnT61String.cs @@ -3,7 +3,7 @@ namespace ASN1; -public sealed class AsnT61String : AsnString +internal sealed class AsnT61String : AsnString { public AsnT61String(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer, string.Empty) { diff --git a/src/facturae/RFC2253/ASN1/AsnTag.cs b/src/facturae/RFC2253/ASN1/AsnTag.cs index 950ff13..75beb40 100644 --- a/src/facturae/RFC2253/ASN1/AsnTag.cs +++ b/src/facturae/RFC2253/ASN1/AsnTag.cs @@ -6,7 +6,7 @@ namespace ASN1; /// /// http://en.wikipedia.org/wiki/X.690 /// -public enum AsnTag +internal enum AsnTag { EOC = 0x00, Boolean = 0x01, diff --git a/src/facturae/RFC2253/ASN1/AsnUTCTime.cs b/src/facturae/RFC2253/ASN1/AsnUTCTime.cs index f7ad53b..1e41913 100644 --- a/src/facturae/RFC2253/ASN1/AsnUTCTime.cs +++ b/src/facturae/RFC2253/ASN1/AsnUTCTime.cs @@ -6,7 +6,7 @@ namespace ASN1; -public sealed class AsnUTCTime : AsnValueObject +internal sealed class AsnUTCTime : AsnValueObject { private static readonly string[] s_timeFormats = [ diff --git a/src/facturae/RFC2253/ASN1/AsnUTF8String.cs b/src/facturae/RFC2253/ASN1/AsnUTF8String.cs index 06e7ba4..bde3645 100644 --- a/src/facturae/RFC2253/ASN1/AsnUTF8String.cs +++ b/src/facturae/RFC2253/ASN1/AsnUTF8String.cs @@ -5,7 +5,7 @@ namespace ASN1; -public sealed class AsnUTF8String : AsnString +internal sealed class AsnUTF8String : AsnString { public AsnUTF8String(AsnIdentifier id, ReadOnlyMemory buffer) : base(id, buffer, Encoding.UTF8.GetString(buffer.Span)) diff --git a/src/facturae/RFC2253/ASN1/AsnValueObjectOfT.cs b/src/facturae/RFC2253/ASN1/AsnValueObjectOfT.cs index d264f2d..10388d8 100644 --- a/src/facturae/RFC2253/ASN1/AsnValueObjectOfT.cs +++ b/src/facturae/RFC2253/ASN1/AsnValueObjectOfT.cs @@ -3,7 +3,7 @@ namespace ASN1; -public abstract class AsnValueObject : AsnObject, IAsnValueObject +internal abstract class AsnValueObject : AsnObject, IAsnValueObject { public T Value { diff --git a/src/facturae/RFC2253/ASN1/IAsnValueObject.cs b/src/facturae/RFC2253/ASN1/IAsnValueObject.cs index 7123af4..4c5e6d9 100644 --- a/src/facturae/RFC2253/ASN1/IAsnValueObject.cs +++ b/src/facturae/RFC2253/ASN1/IAsnValueObject.cs @@ -3,7 +3,7 @@ namespace ASN1; -public interface IAsnValueObject +internal interface IAsnValueObject { object Value { get; } } diff --git a/src/facturae/RFC2253/ASN1/IAsnValueObjectOfT.cs b/src/facturae/RFC2253/ASN1/IAsnValueObjectOfT.cs index 07a929b..2c3d70e 100644 --- a/src/facturae/RFC2253/ASN1/IAsnValueObjectOfT.cs +++ b/src/facturae/RFC2253/ASN1/IAsnValueObjectOfT.cs @@ -3,7 +3,7 @@ namespace ASN1; -public interface IAsnValueObject : IAsnValueObject +internal interface IAsnValueObject : IAsnValueObject { new T Value { get; } } diff --git a/src/facturae/RFC2253/X500/DistinguishedNameFormat.cs b/src/facturae/RFC2253/X500/DistinguishedNameFormat.cs index 6f1347a..924b235 100644 --- a/src/facturae/RFC2253/X500/DistinguishedNameFormat.cs +++ b/src/facturae/RFC2253/X500/DistinguishedNameFormat.cs @@ -3,7 +3,7 @@ namespace X500; -public enum DistinguishedNameFormat +internal enum DistinguishedNameFormat { RFC1779, RFC2253, diff --git a/src/facturae/RFC2253/X500/X500DistinguishedName.cs b/src/facturae/RFC2253/X500/X500DistinguishedName.cs index 0f5f4c4..55f0bec 100644 --- a/src/facturae/RFC2253/X500/X500DistinguishedName.cs +++ b/src/facturae/RFC2253/X500/X500DistinguishedName.cs @@ -6,7 +6,7 @@ namespace X500; -public static class X500DistinguishedName +internal static class X500DistinguishedName { private static readonly Dictionary s_rfc1179; private static readonly Dictionary s_rfc2253; diff --git a/src/facturae/XAdES/XAdESSignatureVerifier.cs b/src/facturae/XAdES/XAdESSignatureVerifier.cs index 6b5211d..189903d 100644 --- a/src/facturae/XAdES/XAdESSignatureVerifier.cs +++ b/src/facturae/XAdES/XAdESSignatureVerifier.cs @@ -28,13 +28,34 @@ public XAdESSignatureVerifier(XmlDocument document) /// /// The target file path /// An instance of - public XAdESSignatureVerifier WriteToFile(string path) + public XAdESSignatureVerifier Save(string path) { _signedDocument.Save(path); return this; } + /// + /// Saves the XML document to the specified System.Xml.XmlWriter. + /// + public XAdESSignatureVerifier Save(XmlWriter w) + { + _signedDocument.Save(w); + + return this; + } + + /// + /// Saves the XML document to the specified stream. + /// + public XAdESSignatureVerifier Save(Stream outStream) + { + _signedDocument.Save(outStream); + + return this; + } + + /// /// Verify the signature against an asymetric /// algorithm and return the result. diff --git a/src/facturae/XAdES/XAdESSignedXml.cs b/src/facturae/XAdES/XAdESSignedXml.cs index 6aa335f..d250662 100644 --- a/src/facturae/XAdES/XAdESSignedXml.cs +++ b/src/facturae/XAdES/XAdESSignedXml.cs @@ -13,7 +13,7 @@ namespace FacturaE.XAdES; /// /// Custom implementation. /// -public sealed class XAdESSignedXml : SignedXml +internal sealed class XAdESSignedXml : SignedXml { private readonly List _dataObjects = []; private ClaimedRole _signerRole; diff --git a/src/facturae/Certificates/facturae.p12 b/tests/facturae.tests/Certificates/facturae.p12 similarity index 100% rename from src/facturae/Certificates/facturae.p12 rename to tests/facturae.tests/Certificates/facturae.p12 diff --git a/tests/facturae.tests/FacturaE.UnitTests.cs b/tests/facturae.tests/FacturaE.UnitTests.cs new file mode 100644 index 0000000..0185c89 --- /dev/null +++ b/tests/facturae.tests/FacturaE.UnitTests.cs @@ -0,0 +1,219 @@ +using System.Security.Cryptography.X509Certificates; + +using FluentAssertions; + +namespace FacturaE.Tests; + +public sealed class FacturaeTests +{ + [Fact] + public void SignInvoiceTest() + { + var certificate = new X509Certificate2(@"Certificates/facturae.p12", "1234"); + + // Create a new facturae invoice & sign it + var instance = new Facturae() + .Seller() + .SetIdentification("00001") + .AsResidentInSpain() + .SetIdentificationNumber("35799562Q") + .AsIndividual() + .SetName("JOHN") + .SetFirstSurname("DOE") + .SetAddress("8585 FIRST STREET") + .SetProvince("MADRID") + .SetTown("MADRID") + .SetPostCode("99900") + .SetCountryCode(CountryType.ESP) + .Party() + .Invoice() + .Buyer() + .SetIdentification("00002") + .AsResidentInSpain() + .SetIdentificationNumber("06990097Y") + .AsLegalEntity() + .SetCorporateName("JOHN") + .SetAddress("8585 FIRST STREET") + .SetProvince("MADRID") + .SetTown("MADRID") + .SetPostCode("99900") + .SetCountryCode(CountryType.ESP) + .Party() + .AddAdministrativeCentre() + .SetCentreCode("1") + .SetRoleCodeType("02") + .SetLogicalOperationalPoint("1233") + .SetName("ADMINISTRATION NAME") + .SetAddress("1234 Street") + .SetProvince("MADRID") + .SetTown("MADRID") + .SetPostCode("99900") + .SetCountryCode(CountryType.ESP) + .Party() + .Invoice() + .CreateInvoice() + .SetCurrency(CurrencyCodeType.EUR) + .SetExchangeRate(1, DateTime.Today) + .SetTaxCurrency(CurrencyCodeType.EUR) + .SetLanguage(LanguageCodeType.es) + .SetPlaceOfIssue(string.Empty, "00000") + .IsOriginal() + .IsComplete() + .SetInvoiceSeries("IN") + .SetInvoiceNumber("1000") + .AddInvoiceItem("XX", "XX") + .GiveQuantity(1.0M) + .GiveUnitPriceWithoutTax(100.01M) + .GiveDiscount(10.01M, "Line Discount") + .GiveValueAddedTaxRate(21.0M, 5.20M) + .GiveTaxRate(9.0M, TaxTypeCodeType.PersonalIncomeTax) + .CalculateTotals() + .AddInvoiceItem("XXX", "XXX") + .GiveQuantity(1) + .GiveUnitPriceWithoutTax(100.01M) + .GiveDiscount(10.01M) + .GiveValueAddedTaxRate(21.0M, 5.20M) + .GiveTaxRate(9.0M, TaxTypeCodeType.PersonalIncomeTax) + .CalculateTotals() + .CalculateTotals() + .CalculateTotals(); + + var exception1 = Record.Exception(() => instance.Validate()); + + Assert.Null(exception1); + + var verifier = instance.Sign(certificate); + var exception2 = Record.Exception(() => verifier.CheckSignature()); + + Assert.Null(exception2); + } + + [Fact] + public void GeneralRegimenTaxesTest() + { + var instance = new Facturae() + .SetIssuer(InvoiceIssuerTypeType.Seller) + .Seller() + .SetIdentification("00001") + .AsResidentInSpain() + .SetIdentificationNumber("A2800056F") + .AsLegalEntity() + .SetCorporateName("Public Limited Company") + .SetAddress("Street Alcalá 137") + .SetProvince("MADRID") + .SetTown("MADRID") + .SetPostCode("28001") + .SetCountryCode(CountryType.ESP) + .Party() + .Invoice() + .Buyer() + .SetIdentification("00002") + .AsResidentInSpain() + .SetIdentificationNumber("A4155543L") + .AsLegalEntity() + .SetCorporateName("Prima S.A.") + .SetAddress("Street San Vicente 1") + .SetProvince("Seville") + .SetTown("Seville") + .SetPostCode("41008") + .SetCountryCode(CountryType.ESP) + .Party() + .AddAdministrativeCentre() + .SetCentreCode("1") + .SetRoleCodeType("02") + .SetLogicalOperationalPoint("1233") + .SetName("ADMINISTRATION NAME") + .SetAddress("1234 Street") + .SetProvince("MADRID") + .SetTown("MADRID") + .SetPostCode("99900") + .SetCountryCode(CountryType.ESP) + .Party() + .Invoice(); + + var invoiceType = instance + .CreateInvoice() + .SetInvoiceNumber("BX-375-09") + .SetIssueDate(new DateTime(2009, 03, 02)) + .SetCurrency(CurrencyCodeType.EUR) + .SetTaxCurrency(CurrencyCodeType.EUR) + .SetLanguage(LanguageCodeType.es) + .SetExchangeRate(1, DateTime.Today) + .SetPlaceOfIssue(string.Empty, "00000") + .IsOriginal() + .IsComplete(); + + var firstItem = invoiceType + .AddInvoiceItem() + .SetIssuerContractReference("C070801") + .SetIssuerTransactionReference("C0107") + .SetDescription("Notepads") + .GiveQuantity(500.0M) + .GiveUnitOfMeasure(UnitOfMeasureType.Units) + .GiveUnitPriceWithoutTax(9.156M) + .GiveValueAddedTaxRate(16.0M); + + firstItem.CalculateTotals(); + + firstItem.TaxesOutputs.Count.Should().Be(1); + firstItem.TotalCost.Should().Be(4578.0M); + firstItem.GrossAmount.Should().Be(4578.0M); + + var fto = firstItem.TaxesOutputs[0]; + + fto.Should().NotBeNull(); + + fto.TaxTypeCode.Should().Be(TaxTypeCodeType.ValueAddedTax); + fto.TaxRate.Should().Be(16.0M); + fto.TaxableBase.Should().NotBeNull(); + fto.TaxableBase.TotalAmount.Should().Be(4578.0M); + fto.TaxAmount.TotalAmount.Should().Be(732.48M); + + var secondItem = invoiceType + .AddInvoiceItem() + .SetIssuerContractReference("C070801") + .SetIssuerTransactionReference("C0107") + .SetDescription("Books") + .GiveQuantity(60.0M) + .GiveUnitOfMeasure(UnitOfMeasureType.Units) + .GiveUnitPriceWithoutTax(35M) + .GiveValueAddedTaxRate(7.0M); + + secondItem.CalculateTotals(); + + secondItem.TaxesOutputs.Count.Should().Be(1); + secondItem.TotalCost.Should().Be(2100.0M); + secondItem.GrossAmount.Should().Be(2100.0M); + + var sto = secondItem.TaxesOutputs[0]; + + sto.Should().NotBeNull(); + + sto.TaxTypeCode.Should().Be(TaxTypeCodeType.ValueAddedTax); + sto.TaxRate.Should().Be(7.0M); + sto.TaxableBase.Should().NotBeNull(); + sto.TaxableBase.TotalAmount.Should().Be(2100.0M); + sto.TaxAmount.TotalAmount.Should().Be(147.0M); + + invoiceType.CalculateTotals(); + + invoiceType.InvoiceTotals.Should().NotBeNull(); + invoiceType.InvoiceTotals.TotalGrossAmount.Should().Be(6678.0M); + invoiceType.InvoiceTotals.TotalGrossAmountBeforeTaxes.Should().Be(6678.0M); + invoiceType.InvoiceTotals.TotalTaxOutputs.Should().Be(879.48M); + invoiceType.InvoiceTotals.TotalTaxesWithheld.Should().Be(0.0M); + invoiceType.InvoiceTotals.InvoiceTotal.Should().Be(7557.48M); + invoiceType.InvoiceTotals.TotalOutstandingAmount.Should().Be(7557.48M); + invoiceType.InvoiceTotals.TotalExecutableAmount.Should().Be(7557.48M); + + instance.CalculateTotals(); + + instance.FileHeader.Batch.Should().NotBeNull(); + instance.FileHeader.Batch.BatchIdentifier.Should().Be("A2800056FBX-375-09"); + instance.FileHeader.Batch.InvoicesCount.Should().Be(1); + instance.FileHeader.Batch.TotalInvoicesAmount.TotalAmount.Should().Be(7557.48M); + instance.FileHeader.Batch.TotalOutstandingAmount.TotalAmount.Should().Be(7557.48M); + instance.FileHeader.Batch.TotalExecutableAmount.TotalAmount.Should().Be(7557.48M); + instance.FileHeader.Batch.InvoiceCurrencyCode.Should().Be(CurrencyCodeType.EUR); + } +} \ No newline at end of file diff --git a/tests/facturae.tests/facturae.tests.csproj b/tests/facturae.tests/facturae.tests.csproj new file mode 100644 index 0000000..7a57356 --- /dev/null +++ b/tests/facturae.tests/facturae.tests.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + + FacturaE.UnitTests + + false + true + + + + + + + + + + + + + + + + + Always + + + + + + + +