From 489b2afc043a99f53375a2e4f37993826f81e42a Mon Sep 17 00:00:00 2001 From: abdallahbeshi Date: Wed, 31 Jan 2024 12:42:21 +0100 Subject: [PATCH 1/2] WIP --- .../InvoiceTestData.cs | 79 +++++++++++------ .../SendInvoiceTests.cs | 69 ++++++++++----- .../DtoToModelConverter.cs | 2 +- .../Mews.Fiscalizations.Basque.csproj | 2 +- .../Model/ErrorCode.cs | 4 + .../Model/Invoice/Invoice.cs | 50 ++++++++++- .../Model/Invoice/InvoiceData.cs | 27 ++---- .../Model/Invoice/InvoiceHeader.cs | 6 +- .../Model/SendInvoiceRequest.cs | 87 ++++++++++++++++++- .../Model/Subject.cs | 19 ++-- .../ModelToDtoConverter.cs | 14 +-- .../TicketBaiClient.cs | 60 ++++++++++--- .../Mews.Fiscalizations.All.csproj | 2 +- 13 files changed, 312 insertions(+), 109 deletions(-) diff --git a/src/Basque/Mews.Fiscalizations.Basque.Tests/InvoiceTestData.cs b/src/Basque/Mews.Fiscalizations.Basque.Tests/InvoiceTestData.cs index 6ec87ac8..506b57a5 100644 --- a/src/Basque/Mews.Fiscalizations.Basque.Tests/InvoiceTestData.cs +++ b/src/Basque/Mews.Fiscalizations.Basque.Tests/InvoiceTestData.cs @@ -1,43 +1,79 @@ namespace Mews.Fiscalizations.Basque.Tests; -internal sealed class InvoiceTestData +internal static class InvoiceTestData { - internal static SendInvoiceRequest CreateInvoiceRequest(Issuer issuer, Software software, bool localReceivers, bool negativeInvoice, OriginalInvoiceInfo originalInvoiceInfo = null) + internal static SendInvoiceRequest CreateCompleteInvoiceRequest( + Issuer issuer, + Software software, + bool localReceivers, + bool negativeInvoice, + OriginalInvoiceInfo originalInvoiceInfo = null) { - return new SendInvoiceRequest( - subject: CreateSubject(issuer, localReceivers), - invoice: CreateInvoice(localReceivers, negativeInvoice), - invoiceFooter: new InvoiceFooter(software, originalInvoiceInfo: originalInvoiceInfo) + var taxSummary = CreateTaxSummary(negativeInvoice); + var randomString = String1To20.CreateUnsafe(Guid.NewGuid().ToString()[..19]); + return localReceivers.Match( + t => SendInvoiceRequest.CreateCompleteLocalReceiverInvoiceRequest( + issuer: issuer, + invoiceFooter: new InvoiceFooter(software, originalInvoiceInfo: originalInvoiceInfo), + receivers: CreateReceivers(localReceivers), + invoiceData: CreateInvoiceData(negativeInvoice), + taxSummary: taxSummary, + number: randomString, + issued: DateTime.Now, + series: randomString, + issuerType: IssuerType.IssuedByThirdParty + ), + f => SendInvoiceRequest.CreateCompleteForeignReceiverInvoiceRequest( + issuer: issuer, + invoiceFooter: new InvoiceFooter(software, originalInvoiceInfo: originalInvoiceInfo), + receivers: CreateReceivers(localReceivers), + invoiceData: CreateInvoiceData(negativeInvoice), + taxBreakdown: OperationTypeTaxBreakdown.Create(delivery: taxSummary).Success.Get(), + number: randomString, + issued: DateTime.Now, + series: randomString, + issuerType: IssuerType.IssuedByThirdParty + ) ); } - private static Invoice CreateInvoice(bool localReceivers, bool negativeInvoice) + internal static SendInvoiceRequest CreateSimplifiedInvoiceRequest( + Issuer issuer, + Software software, + bool negativeInvoice, + OriginalInvoiceInfo originalInvoiceInfo = null) { - return new Invoice(CreateHeader(), CreateInvoiceData(negativeInvoice), CreateTaxBreakdown(localReceivers, negativeInvoice)); + var randomString = String1To20.CreateUnsafe(Guid.NewGuid().ToString()[..19]); + return SendInvoiceRequest.CreateSimplifiedInvoiceRequest( + issuer: issuer, + invoiceFooter: new InvoiceFooter(software, originalInvoiceInfo: originalInvoiceInfo), + invoiceData: CreateInvoiceData(negativeInvoice), + taxSummary: CreateTaxSummary(negativeInvoice), + number: randomString, + issued: DateTime.Now, + series: randomString, + issuerType: IssuerType.IssuedByThirdParty + ); } - private static TaxBreakdown CreateTaxBreakdown(bool localReceivers, bool negativeInvoice) + private static TaxSummary CreateTaxSummary(bool negativeInvoice) { var baseValue = negativeInvoice.Match( t => -73.86m, f => 73.86m ); - var taxSummary = TaxSummary.Create(taxed: CreateTaxRateSummary(21m, baseValue).ToEnumerable().ToArray()).Success.Get(); - return localReceivers.Match( - t => new TaxBreakdown(taxSummary), - f => new TaxBreakdown(OperationTypeTaxBreakdown.Create(delivery: taxSummary).Success.Get()) - ); + return TaxSummary.Create(taxed: CreateTaxRateSummary(21m, baseValue).ToEnumerable().ToArray()).Success.Get(); } private static InvoiceData CreateInvoiceData(bool negativeInvoice) { - return InvoiceData.Create( + return new InvoiceData( description: String1To250.CreateUnsafe("TicketBAI sample invoice test."), items: CreateInvoiceItems(negativeInvoice), totalAmount: negativeInvoice.Match(t => -89.36m, f => 89.36m), taxModes: TaxMode.GeneralTaxRegimeActivity.ToEnumerable(), transactionDate: DateTime.Now - ).Success.Get(); + ); } private static INonEmptyEnumerable CreateInvoiceItems(bool negativeInvoice) @@ -88,17 +124,6 @@ private static INonEmptyEnumerable CreateInvoiceItems(bool negative ); } - private static InvoiceHeader CreateHeader() - { - var randomString = String1To20.CreateUnsafe(Guid.NewGuid().ToString().Substring(0, 19)); - return new InvoiceHeader(number: randomString, issued: DateTime.Now, series: randomString); - } - - private static Subject CreateSubject(Issuer issuer, bool localReceivers) - { - return Subject.Create(issuer, CreateReceivers(localReceivers), IssuerType.IssuedByThirdParty).Success.Get(); - } - private static INonEmptyEnumerable CreateReceivers(bool localReceivers) { return NonEmptyEnumerable.Create(localReceivers.Match( diff --git a/src/Basque/Mews.Fiscalizations.Basque.Tests/SendInvoiceTests.cs b/src/Basque/Mews.Fiscalizations.Basque.Tests/SendInvoiceTests.cs index 410528b0..639a222a 100644 --- a/src/Basque/Mews.Fiscalizations.Basque.Tests/SendInvoiceTests.cs +++ b/src/Basque/Mews.Fiscalizations.Basque.Tests/SendInvoiceTests.cs @@ -4,50 +4,77 @@ public class SendInvoiceTests { [Test] - [TestCase(Region.Araba, false, false, TestName = "Araba - Send invoice with local receiver")] - [TestCase(Region.Araba, true, false, TestName = "Araba - Send invoice with foreign receiver")] - [TestCase(Region.Araba, false, true, TestName = "Araba - Send negative invoice with local receiver")] - [TestCase(Region.Araba, true, true, TestName = "Araba - Send negative invoice with foreign receiver")] - [TestCase(Region.Gipuzkoa, false, false, TestName = "Gipuzkoa - Send invoice with local receiver")] - [TestCase(Region.Gipuzkoa, true, false, TestName = "Gipuzkoa - Send invoice with foreign receiver")] - [TestCase(Region.Gipuzkoa, false, true, TestName = "Gipuzkoa - Send negative invoice with local receiver")] - [TestCase(Region.Gipuzkoa, true, true, TestName = "Gipuzkoa - Send negative invoice with foreign receiver")] - [TestCase(Region.Bizkaia, false, false, TestName = "Bizkaia - Send invoice with local receiver")] - [TestCase(Region.Bizkaia, true, false, TestName = "Bizkaia - Send invoice with foreign receiver")] - [TestCase(Region.Bizkaia, false, true, TestName = "Bizkaia - Send negative invoice with local receiver")] - [TestCase(Region.Bizkaia, true, true, TestName = "Bizkaia - Send negative invoice with foreign receiver")] + [TestCase(Region.Araba, true, false, TestName = "Araba - Send complete invoice with local receiver")] + [TestCase(Region.Araba, false, false, TestName = "Araba - Send complete invoice with foreign receiver")] + [TestCase(Region.Araba, true, true, TestName = "Araba - Send negative complete invoice with local receiver")] + [TestCase(Region.Araba, false, true, TestName = "Araba - Send negative complete invoice with foreign receiver")] + [TestCase(Region.Gipuzkoa, true, false, TestName = "Gipuzkoa - Send complete invoice with local receiver")] + [TestCase(Region.Gipuzkoa, false, false, TestName = "Gipuzkoa - Send complete invoice with foreign receiver")] + [TestCase(Region.Gipuzkoa, true, true, TestName = "Gipuzkoa - Send complete negative invoice with local receiver")] + [TestCase(Region.Gipuzkoa, false, true, TestName = "Gipuzkoa - Send complete negative invoice with foreign receiver")] + [TestCase(Region.Bizkaia, true, false, TestName = "Bizkaia - Send complete invoice with local receiver")] + [TestCase(Region.Bizkaia, false, false, TestName = "Bizkaia - Send complete invoice with foreign receiver")] + [TestCase(Region.Bizkaia, true, true, TestName = "Bizkaia - Send complete negative invoice with local receiver")] + [TestCase(Region.Bizkaia, false, true, TestName = "Bizkaia - Send complete negative invoice with foreign receiver")] [Retry(3)] - public async Task SendSimpleInvoiceSucceeds(Region region, bool localReceivers, bool negativeInvoice) + public async Task SendCompleteInvoiceSucceeds(Region region, bool localReceivers, bool negativeInvoice) { var testFixture = new TestFixture(region); var client = testFixture.Client; - var request = InvoiceTestData.CreateInvoiceRequest(testFixture.Issuer, testFixture.Software, localReceivers, negativeInvoice); + var request = InvoiceTestData.CreateCompleteInvoiceRequest(testFixture.Issuer, testFixture.Software, localReceivers, negativeInvoice); + var tbaiData = client.GetTicketBaiInvoiceData(request); + var response = await client.SendInvoiceAsync(tbaiData); + TestFixture.AssertResponse(region, response, tbaiData); + } + + [TestCase(Region.Araba, false, TestName = "Araba - Send simplified invoice")] + [TestCase(Region.Araba, true, TestName = "Araba - Send simplified negative invoice")] + [TestCase(Region.Gipuzkoa, false, TestName = "Gipuzkoa - Send simplified invoice")] + [TestCase(Region.Gipuzkoa, true, TestName = "Gipuzkoa - Send simplified negative invoice")] + [TestCase(Region.Bizkaia, false, TestName = "Bizkaia - Send simplified invoice")] + [TestCase(Region.Bizkaia, true, TestName = "Bizkaia - Send simplified negative invoice")] + [Retry(3)] + public async Task SendSimplifiedInvoiceSucceeds(Region region, bool negativeInvoice) + { + var testFixture = new TestFixture(region); + var client = testFixture.Client; + var request = InvoiceTestData.CreateSimplifiedInvoiceRequest(testFixture.Issuer, testFixture.Software, negativeInvoice); var tbaiData = client.GetTicketBaiInvoiceData(request); var response = await client.SendInvoiceAsync(tbaiData); TestFixture.AssertResponse(region, response, tbaiData); } [Test] - [TestCase(Region.Araba, TestName = "Araba - Invoice chaining")] - [TestCase(Region.Gipuzkoa, TestName = "Gipuzkoa - Invoice chaining")] - [TestCase(Region.Bizkaia, TestName = "Bizkaia - Invoice chaining")] + [TestCase(Region.Araba, false, TestName = "Araba - Complete Invoice chaining")] + [TestCase(Region.Araba, true, TestName = "Araba - Simplified Invoice chaining")] + [TestCase(Region.Gipuzkoa, false, TestName = "Gipuzkoa - Complete Invoice chaining")] + [TestCase(Region.Gipuzkoa, true, TestName = "Gipuzkoa - Simplified Invoice chaining")] + [TestCase(Region.Bizkaia, false, TestName = "Bizkaia - Complete Invoice chaining")] + [TestCase(Region.Bizkaia, true, TestName = "Bizkaia - Simplified Invoice chaining")] [Retry(3)] - public async Task SendChainedInvoiceSucceeds(Region region) + public async Task SendChainedInvoiceSucceeds(Region region, bool isSimplified) { var testFixture = new TestFixture(region); var client = testFixture.Client; - var request1 = InvoiceTestData.CreateInvoiceRequest(testFixture.Issuer, testFixture.Software, localReceivers: true, negativeInvoice: false); + var request1 = isSimplified.Match( + t => InvoiceTestData.CreateSimplifiedInvoiceRequest(testFixture.Issuer, testFixture.Software, negativeInvoice: false), + f => InvoiceTestData.CreateCompleteInvoiceRequest(testFixture.Issuer, testFixture.Software, negativeInvoice: false, localReceivers: true) + ); var tbaiData1 = client.GetTicketBaiInvoiceData(request1); var response1 = await client.SendInvoiceAsync(tbaiData1); TestFixture.AssertResponse(region, response1, tbaiData1); var originalInvoiceHeader = request1.Invoice.Header; - var request2 = InvoiceTestData.CreateInvoiceRequest(testFixture.Issuer, testFixture.Software, localReceivers: true, negativeInvoice: false, originalInvoiceInfo: new OriginalInvoiceInfo( + var originalInvoice = new OriginalInvoiceInfo( number: originalInvoiceHeader.Number, issueDate: originalInvoiceHeader.Issued, signature: response1.SignatureValue, series: originalInvoiceHeader.Series.GetOrNull() - )); + ); + var request2 = isSimplified.Match( + t => InvoiceTestData.CreateSimplifiedInvoiceRequest(testFixture.Issuer, testFixture.Software, negativeInvoice: false, originalInvoice), + f => InvoiceTestData.CreateCompleteInvoiceRequest(testFixture.Issuer, testFixture.Software, localReceivers: true, negativeInvoice: false, originalInvoice) + ); var tbaiData2 = client.GetTicketBaiInvoiceData(request2); var response2 = await client.SendInvoiceAsync(tbaiData2); TestFixture.AssertResponse(region, response2, tbaiData2); diff --git a/src/Basque/Mews.Fiscalizations.Basque/DtoToModelConverter.cs b/src/Basque/Mews.Fiscalizations.Basque/DtoToModelConverter.cs index 07e09ee3..4cbf2963 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/DtoToModelConverter.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/DtoToModelConverter.cs @@ -38,7 +38,7 @@ internal static SendInvoiceResponse Convert( { var record = response.Registros.Single(); var recordStatus = record.SituacionRegistro; - var invoiceIdentifier = record.Identificador.Item as IDFacturaType; // TODO: check if they would ever return byte[] ticketbai. + var invoiceIdentifier = record.Identificador.Item as IDFacturaType; return new SendInvoiceResponse( xmlRequestContent: xmlRequestContent, xmlResponseContent: xmlResponseContent, diff --git a/src/Basque/Mews.Fiscalizations.Basque/Mews.Fiscalizations.Basque.csproj b/src/Basque/Mews.Fiscalizations.Basque/Mews.Fiscalizations.Basque.csproj index dd101b21..de6c94e0 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Mews.Fiscalizations.Basque.csproj +++ b/src/Basque/Mews.Fiscalizations.Basque/Mews.Fiscalizations.Basque.csproj @@ -10,7 +10,7 @@ https://github.com/MewsSystems/fiscalizations https://raw.githubusercontent.com/msigut/eet/master/receipt.png true - 7.0.0 + 8.0.0 12 true diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/ErrorCode.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/ErrorCode.cs index b9a9c4b3..d0a932e6 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/ErrorCode.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/ErrorCode.cs @@ -32,9 +32,13 @@ public enum ErrorCode // Invalid or missing chain. InvalidOrMissingInvoiceChain = 09, + InvalidIssuerNif = 10, + // The rectified (Corrected) invoice is not indicated. CorrectedInvoiceNotIndicated = 011, + InvalidIssuerNameOrNif = 12, + // Issuer NIF must be registered in the Araba region. IssuerNifMustBeRegisteredInArabaRegion = 15, diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/Invoice.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/Invoice.cs index 93c1ad70..cfd5504f 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/Invoice.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/Invoice.cs @@ -2,7 +2,7 @@ public sealed class Invoice { - public Invoice(InvoiceHeader header, InvoiceData invoiceData, TaxBreakdown taxBreakdown) + private Invoice(InvoiceHeader header, InvoiceData invoiceData, TaxBreakdown taxBreakdown) { Header = header; InvoiceData = invoiceData; @@ -14,4 +14,52 @@ public Invoice(InvoiceHeader header, InvoiceData invoiceData, TaxBreakdown taxBr public InvoiceData InvoiceData { get; } public TaxBreakdown TaxBreakdown { get; } + + public static Invoice CreateSimplified( + InvoiceData invoiceData, + TaxSummary taxSummary, + String1To20 number, + DateTime issued, + bool? issuedInSubstitutionOfSimplifiedInvoice = null, + String1To20 series = null, + CorrectingInvoice correctingInvoice = null, + IEnumerable correctedInvoices = null) + { + return new Invoice( + header: new InvoiceHeader( + number: number, + issued: issued, + isSimplified: true, + issuedInSubstitutionOfSimplifiedInvoice: issuedInSubstitutionOfSimplifiedInvoice, + series: series, + correctingInvoice: correctingInvoice, + correctedInvoices: correctedInvoices + ), + invoiceData: invoiceData, + taxBreakdown: new TaxBreakdown(taxSummary) + ); + } + + public static Invoice CreateComplete( + InvoiceData invoiceData, + TaxBreakdown taxBreakdown, + String1To20 number, + DateTime issued, + String1To20 series = null, + CorrectingInvoice correctingInvoice = null, + IEnumerable correctedInvoices = null) + { + return new Invoice( + header: new InvoiceHeader( + number: number, + issued: issued, + isSimplified: false, + series: series, + correctingInvoice: correctingInvoice, + correctedInvoices: correctedInvoices + ), + invoiceData: invoiceData, + taxBreakdown: taxBreakdown + ); + } } \ No newline at end of file diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceData.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceData.cs index 9c02bac3..88b8c573 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceData.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceData.cs @@ -2,11 +2,11 @@ public sealed class InvoiceData { - private InvoiceData( + public InvoiceData( String1To250 description, - IEnumerable items, + INonEmptyEnumerable items, decimal totalAmount, - IEnumerable taxModes, + INonEmptyEnumerable taxModes, decimal? supportWithheldAmount = null, decimal? tax = null, DateTime? transactionDate = null) @@ -22,32 +22,15 @@ private InvoiceData( public String1To250 Description { get; } - public IEnumerable Items { get; } + public INonEmptyEnumerable Items { get; } public decimal TotalAmount { get; } - public IEnumerable TaxModes { get; } + public INonEmptyEnumerable TaxModes { get; } public Option SupportWithheldAmount { get; } public Option Tax { get; } public Option TransactionDate { get; } - - public static Try> Create( - String1To250 description, - IEnumerable items, - decimal totalAmount, - IEnumerable taxModes, - decimal? supportWithheldAmount = null, - decimal? tax = null, - DateTime? transactionDate = null) - { - return Try.Aggregate( - ObjectValidations.NotNull(description), - items.ToList().ToTry(i => i.Any() && i.Count <= 1000, _ => new Error($"{nameof(items)} count must be in range [1, 1000].")), - taxModes.ToTry(t => t.NonEmpty(), _ => new Error($"{nameof(taxModes)} shouldn't be empty.")), - (d, i, t) => new InvoiceData(d, i, totalAmount, t, supportWithheldAmount, tax, transactionDate) - ); - } } \ No newline at end of file diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceHeader.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceHeader.cs index 76373e0c..f36d2a5f 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceHeader.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/Invoice/InvoiceHeader.cs @@ -5,7 +5,7 @@ public sealed class InvoiceHeader public InvoiceHeader( String1To20 number, DateTime issued, - bool? isSimplified = null, + bool isSimplified, bool? issuedInSubstitutionOfSimplifiedInvoice = null, String1To20 series = null, CorrectingInvoice correctingInvoice = null, @@ -13,7 +13,7 @@ public InvoiceHeader( { Number = number; Issued = issued; - IsSimplified = isSimplified.ToOption(); + IsSimplified = isSimplified; IssuedInSubstitutionOfSimplifiedInvoice = issuedInSubstitutionOfSimplifiedInvoice.ToOption(); Series = series.ToOption(); CorrectingInvoice = correctingInvoice.ToOption(); @@ -25,7 +25,7 @@ public InvoiceHeader( public DateTime Issued { get; } - public Option IsSimplified { get; } + public bool IsSimplified { get; } public Option IssuedInSubstitutionOfSimplifiedInvoice { get; } diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/SendInvoiceRequest.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/SendInvoiceRequest.cs index 30af9dd2..3c1c3344 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/SendInvoiceRequest.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/SendInvoiceRequest.cs @@ -2,7 +2,7 @@ public sealed class SendInvoiceRequest { - public SendInvoiceRequest(Subject subject, Invoice invoice, InvoiceFooter invoiceFooter) + private SendInvoiceRequest(Subject subject, Invoice invoice, InvoiceFooter invoiceFooter) { Subject = subject; Invoice = invoice; @@ -14,4 +14,89 @@ public SendInvoiceRequest(Subject subject, Invoice invoice, InvoiceFooter invoic public Invoice Invoice { get; } public InvoiceFooter InvoiceFooter { get; } + + public static SendInvoiceRequest CreateSimplifiedInvoiceRequest( + Issuer issuer, + InvoiceFooter invoiceFooter, + InvoiceData invoiceData, + TaxSummary taxSummary, + String1To20 number, + DateTime issued, + bool? issuedInSubstitutionOfSimplifiedInvoice = null, + String1To20 series = null, + CorrectingInvoice correctingInvoice = null, + IEnumerable correctedInvoices = null, + IssuerType? issuerType = null) + { + return new SendInvoiceRequest( + subject: new Subject(issuer, issuerType: issuerType), + invoice: Invoice.CreateSimplified( + invoiceData: invoiceData, + taxSummary: taxSummary, + number: number, + issued: issued, + issuedInSubstitutionOfSimplifiedInvoice: issuedInSubstitutionOfSimplifiedInvoice, + series: series, + correctingInvoice: correctingInvoice, + correctedInvoices: correctedInvoices + ), + invoiceFooter: invoiceFooter + ); + } + + public static SendInvoiceRequest CreateCompleteLocalReceiverInvoiceRequest( + Issuer issuer, + InvoiceFooter invoiceFooter, + INonEmptyEnumerable receivers, + InvoiceData invoiceData, + TaxSummary taxSummary, + String1To20 number, + DateTime issued, + String1To20 series = null, + CorrectingInvoice correctingInvoice = null, + IEnumerable correctedInvoices = null, + IssuerType? issuerType = null) + { + return new SendInvoiceRequest( + subject: new Subject(issuer, receivers, issuerType), + invoice: Invoice.CreateComplete( + invoiceData: invoiceData, + taxBreakdown: new TaxBreakdown(taxSummary), + number: number, + issued: issued, + series: series, + correctingInvoice: correctingInvoice, + correctedInvoices: correctedInvoices + ), + invoiceFooter: invoiceFooter + ); + } + + public static SendInvoiceRequest CreateCompleteForeignReceiverInvoiceRequest( + Issuer issuer, + InvoiceFooter invoiceFooter, + INonEmptyEnumerable receivers, + InvoiceData invoiceData, + OperationTypeTaxBreakdown taxBreakdown, + String1To20 number, + DateTime issued, + String1To20 series = null, + CorrectingInvoice correctingInvoice = null, + IEnumerable correctedInvoices = null, + IssuerType? issuerType = null) + { + return new SendInvoiceRequest( + subject: new Subject(issuer, receivers, issuerType), + invoice: Invoice.CreateComplete( + invoiceData: invoiceData, + taxBreakdown: new TaxBreakdown(taxBreakdown), + number: number, + issued: issued, + series: series, + correctingInvoice: correctingInvoice, + correctedInvoices: correctedInvoices + ), + invoiceFooter: invoiceFooter + ); + } } \ No newline at end of file diff --git a/src/Basque/Mews.Fiscalizations.Basque/Model/Subject.cs b/src/Basque/Mews.Fiscalizations.Basque/Model/Subject.cs index dd6962e0..168a8a8b 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/Model/Subject.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/Model/Subject.cs @@ -2,28 +2,19 @@ public sealed class Subject { - private Subject(Issuer issuer, IEnumerable receivers, IssuerType? issuerType = null) + public Subject(Issuer issuer, IEnumerable receivers = null, IssuerType? issuerType = null) { Issuer = issuer; - Receivers = receivers; - MultipleReceivers = Receivers.Count() > 1; + Receivers = receivers.ToOption(); + MultipleReceivers = Receivers.Map(r => r.Count() > 1); IssuerType = issuerType.ToOption(); } public Issuer Issuer { get; } - public IEnumerable Receivers { get; } + public Option> Receivers { get; } - public bool MultipleReceivers { get; } + public Option MultipleReceivers { get; } public Option IssuerType { get; } - - public static Try> Create(Issuer issuer, IEnumerable receivers, IssuerType? issuerType = null) - { - return Try.Aggregate( - ObjectValidations.NotNull(issuer), - receivers.ToList().ToTry(i => i.Any() && i.Count <= 100, _ => new Error($"{nameof(receivers)} count must be in range [1, 100].")), - (i, r) => new Subject(i, r, issuerType) - ); - } } \ No newline at end of file diff --git a/src/Basque/Mews.Fiscalizations.Basque/ModelToDtoConverter.cs b/src/Basque/Mews.Fiscalizations.Basque/ModelToDtoConverter.cs index e5f6cc19..a53098d0 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/ModelToDtoConverter.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/ModelToDtoConverter.cs @@ -27,12 +27,12 @@ private static Dto.Sujetos Convert(Subject subject) return new Dto.Sujetos { Emisor = Convert(subject.Issuer), - Destinatarios = subject.Receivers.Select(r => Convert(r)).ToArray(), - VariosDestinatariosSpecified = true, - VariosDestinatarios = subject.MultipleReceivers.Match( + Destinatarios = subject.Receivers.GetOrNull(r => r.Select(r => Convert(r)).ToArray()), + VariosDestinatariosSpecified = subject.MultipleReceivers.NonEmpty, + VariosDestinatarios = subject.MultipleReceivers.Map(r => r.Match( t => Dto.SiNoType.S, f => Dto.SiNoType.N - ), + )).GetOrElse(Dto.SiNoType.N), EmitidaPorTercerosODestinatario = subject.IssuerType.Map(t => Convert(t)).ToNullable(), EmitidaPorTercerosODestinatarioSpecified = subject.IssuerType.NonEmpty }; @@ -257,11 +257,11 @@ private static Dto.CabeceraFacturaType1 Convert(InvoiceHeader header) NumFactura = header.Number.Value, FechaExpedicionFactura = Convert(header.Issued.Date), HoraExpedicionFactura = header.Issued.ToString("HH:MM:ss"), - FacturaSimplificada = header.IsSimplified.Map(i => i.Match( + FacturaSimplificada = header.IsSimplified.Match( t => Dto.SiNoType.S, f => Dto.SiNoType.N - )).GetOrElse(Dto.SiNoType.N), - FacturaSimplificadaSpecified = header.IsSimplified.NonEmpty, + ), + FacturaSimplificadaSpecified = true, FacturaEmitidaSustitucionSimplificada = header.IssuedInSubstitutionOfSimplifiedInvoice.Map(i => i.Match( t => Dto.SiNoType.S, f => Dto.SiNoType.N diff --git a/src/Basque/Mews.Fiscalizations.Basque/TicketBaiClient.cs b/src/Basque/Mews.Fiscalizations.Basque/TicketBaiClient.cs index 6083d15a..51dc049f 100644 --- a/src/Basque/Mews.Fiscalizations.Basque/TicketBaiClient.cs +++ b/src/Basque/Mews.Fiscalizations.Basque/TicketBaiClient.cs @@ -71,22 +71,51 @@ private async Task SendBizkaiaInvoiceAsync(TicketBaiInvoice var response = await HttpClient.SendAsync(requestMessage, cancellationToken); var responseContent = await response.Content.ReadAsStringAsync(cancellationToken); - if (string.IsNullOrEmpty(responseContent)) + if (response.IsSuccessStatusCode) { - throw new HttpRequestException($"Received an empty response after sending request with response headers: {response.Headers}"); + if (response.Headers.TryGetValues("eus-bizkaia-n3-tipo-respuesta", out var responseStatus)) + { + if (responseStatus.First() != "Correcto" && string.IsNullOrEmpty(responseContent)) + { + var errorCode = string.Join(',', response.Headers.GetValues("eus-bizkaia-n3-codigo-respuesta")); + var errorMessage = string.Join(',', response.Headers.GetValues("eus-bizkaia-n3-mensaje-respuesta")); + return new SendInvoiceResponse( + xmlRequestContent: invoiceData.SignedRequest.OuterXml, + xmlResponseContent: "", + qrCodeUri: invoiceData.QrCodeUri, + tbaiIdentifier: invoiceData.TbaiIdentifier, + received: DateTime.Now, + state: InvoiceState.Refused, + description: errorMessage, + signatureValue: invoiceData.TrimmedSignature, + validationResults: [ new SendInvoiceValidationResult(MapErrorCode(errorCode), errorMessage) ] + ); + } + + var lroeResponse = XmlSerializer.Deserialize(responseContent); + return DtoToModelConverter.Convert( + response: lroeResponse, + qrCodeUri: invoiceData.QrCodeUri, + xmlRequestContent: invoiceData.SignedRequest.OuterXml, + xmlResponseContent: responseContent, + tbaiIdentifier: invoiceData.TbaiIdentifier, + signatureValue: invoiceData.TrimmedSignature + ); + } } - - var lroeResponse = XmlSerializer.Deserialize(responseContent); - return DtoToModelConverter.Convert( - response: lroeResponse, - qrCodeUri: invoiceData.QrCodeUri, + return new SendInvoiceResponse( xmlRequestContent: invoiceData.SignedRequest.OuterXml, - xmlResponseContent: responseContent, + xmlResponseContent: "", + qrCodeUri: invoiceData.QrCodeUri, tbaiIdentifier: invoiceData.TbaiIdentifier, - signatureValue: invoiceData.TrimmedSignature + received: DateTime.Now, + state: InvoiceState.Refused, + description: $"Response status code: {response.StatusCode}, Content: {responseContent}", + signatureValue: invoiceData.TrimmedSignature, + validationResults: [ new SendInvoiceValidationResult(ErrorCode.ServerErrorTryAgain, $"Response status code: {response.StatusCode}, Content: {responseContent}") ] ); } - + private async Task SendTicketBaiInvoiceAsync(TicketBaiInvoiceData invoiceData) { var signedRequest = invoiceData.SignedRequest; @@ -184,4 +213,15 @@ private SignatureDocument GetSignedInvoiceDocument(XmlDocument doc) } return signatureDocument; } + + private static ErrorCode MapErrorCode(string code) + { + return code switch + { + "N3_0000001" => ErrorCode.InvalidIssuerCertificate, + "N3_0000002" => ErrorCode.InvalidIssuerNameOrNif, + "N3_0000010" => ErrorCode.InvalidIssuerNif, + _ => throw new NotImplementedException($"{code} is not implemented.") + }; + } } \ No newline at end of file diff --git a/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj b/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj index a84ef620..32a17966 100644 --- a/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj +++ b/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj @@ -10,7 +10,7 @@ https://github.com/MewsSystems/fiscalizations https://raw.githubusercontent.com/msigut/eet/master/receipt.png true - 26.0.1 + 27.0.0 12 true $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage From 133b48e0ca250cbff921664c56b467245ca974dc Mon Sep 17 00:00:00 2001 From: Abdallah Altrabeishi <51375082+abdallahbeshi@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:19:53 +0100 Subject: [PATCH 2/2] CR --- src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj b/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj index 32a17966..82f1f6dd 100644 --- a/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj +++ b/src/Mews.Fiscalizations.All/Mews.Fiscalizations.All.csproj @@ -39,7 +39,7 @@ - +