diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55e519c..138a536 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: run: dotnet test --no-build --settings coverlet.runsettings --verbosity normal - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests diff --git a/CHANGELOG.md b/CHANGELOG.md index c3476ef..98dfb68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.15.0 | 2024-09-13 +* Add support for Send an invoice operation in the `Sales invoice` endpoint + ## 0.14.0 | 2024-07-07 * Add support for `Time entry` endpoint * Refactoring of the `Note` entity from `ContactNote` to a generic `Note` used in multiple entity types @@ -37,7 +40,7 @@ ## 0.9.0 | 2023-06-04 * Add support for `Tax rates` endpoint. * Add support for `Ledger account` endpoint. -* Add support for `Sales invoices endpoint` endpoint (limited operations). +* Add support for `Sales invoices` endpoint endpoint (limited operations). * Add support for `External sales invoices` endpoint (limited operations). * Change names (classes, enums, interfaces) and their namespaces. diff --git a/src/Moneybird.Net/Endpoints/Abstractions/ISalesInvoiceEndpoint.cs b/src/Moneybird.Net/Endpoints/Abstractions/ISalesInvoiceEndpoint.cs index e48877d..32e70eb 100644 --- a/src/Moneybird.Net/Endpoints/Abstractions/ISalesInvoiceEndpoint.cs +++ b/src/Moneybird.Net/Endpoints/Abstractions/ISalesInvoiceEndpoint.cs @@ -14,6 +14,16 @@ public interface ISalesInvoiceEndpoint : IUpdateEndpoint, IDeleteEndpoint { + /// + /// Send an invoice to a customer. + /// + /// The administration id. + /// The sales invoice id. + /// The options to send the invoice with. + /// The access token. + /// + Task SendInvoice(string administrationId, string salesInvoiceId, SalesInvoiceSendOptions options, string accessToken); + /// /// Add an attachment to a sales invoice. /// diff --git a/src/Moneybird.Net/Endpoints/SalesInvoiceEndpoint.cs b/src/Moneybird.Net/Endpoints/SalesInvoiceEndpoint.cs index 79f2078..fe14f87 100644 --- a/src/Moneybird.Net/Endpoints/SalesInvoiceEndpoint.cs +++ b/src/Moneybird.Net/Endpoints/SalesInvoiceEndpoint.cs @@ -14,6 +14,7 @@ public class SalesInvoiceEndpoint : ISalesInvoiceEndpoint { private const string SalesInvoiceUri = "/{0}/sales_invoices.json"; private const string SalesInvoiceIdUri = "/{0}/sales_invoices/{1}.json"; + private const string SalesInvoiceSendInvoiceUri = "/{0}/sales_invoices/{1}/send_invoice.json"; private const string SalesInvoiceAttachmentUri = "/{0}/external_sales_invoices/{1}/attachment"; private readonly MoneybirdConfig _config; @@ -96,6 +97,17 @@ public async Task DeleteByIdAsync(string administrationId, string salesInv return response; } + public async Task SendInvoice(string administrationId, string salesInvoiceId, SalesInvoiceSendOptions options, string accessToken) + { + var relativeUrl = string.Format(SalesInvoiceSendInvoiceUri, administrationId, salesInvoiceId); + var body = JsonSerializer.Serialize(options, _config.SerializerOptions); + var responseJson = await _requester + .CreatePatchRequestAsync(_config.ApiUri, relativeUrl, accessToken, body) + .ConfigureAwait(false); + + return JsonSerializer.Deserialize(responseJson, _config.SerializerOptions); ; + } + public async Task AddAttachmentAsync(string administrationId, string salesInvoiceId, Stream body, string accessToken, string fileName = "invoice.pdf") { var relativeUrl = string.Format(SalesInvoiceAttachmentUri, administrationId, salesInvoiceId); diff --git a/src/Moneybird.Net/Models/SalesInvoices/SalesInvoiceSendOptions.cs b/src/Moneybird.Net/Models/SalesInvoices/SalesInvoiceSendOptions.cs new file mode 100644 index 0000000..53f7192 --- /dev/null +++ b/src/Moneybird.Net/Models/SalesInvoices/SalesInvoiceSendOptions.cs @@ -0,0 +1,29 @@ +using Moneybird.Net.Misc; +using System.Text.Json.Serialization; + +namespace Moneybird.Net.Models.SalesInvoices +{ + public class SalesInvoiceSendOptions + { + [JsonPropertyName("delivery_method")] + public DeliveryMethod DeliveryMethod { get; set; } + + [JsonPropertyName("sending_scheduled")] + public bool SendingScheduled { get; set; } + + [JsonPropertyName("deliver_ubl")] + public bool DeliverUbl { get; set; } + + [JsonPropertyName("mergable")] + public bool Mergeable { get; set; } + + [JsonPropertyName("email_address")] + public string EmailAddress { get; set; } + + [JsonPropertyName("email_message")] + public string EmailMessage { get; set; } + + [JsonPropertyName("invoice_date")] + public string InvoiceDate { get; set; } + } +} diff --git a/src/Moneybird.Net/Moneybird.Net.csproj b/src/Moneybird.Net/Moneybird.Net.csproj index 0e9e759..fac09dd 100644 --- a/src/Moneybird.Net/Moneybird.Net.csproj +++ b/src/Moneybird.Net/Moneybird.Net.csproj @@ -3,7 +3,7 @@ netstandard2.0 9 Moneybird.Net - 0.14.0 + 0.15.0 Vincent Vrijburg A wrapper for the Moneybird API. Copyright © Vincent Vrijburg 2021 @@ -12,12 +12,12 @@ dotnet dotnet-core dotnet-standard client wrapper moneybird MIT false - 0.14.0 + 0.15.0 - + diff --git a/tests/Moneybird.Net.Tests/Endpoints/SalesInvoiceEndpointTests.cs b/tests/Moneybird.Net.Tests/Endpoints/SalesInvoiceEndpointTests.cs index 7b3890f..c28783a 100644 --- a/tests/Moneybird.Net.Tests/Endpoints/SalesInvoiceEndpointTests.cs +++ b/tests/Moneybird.Net.Tests/Endpoints/SalesInvoiceEndpointTests.cs @@ -8,6 +8,7 @@ using Moneybird.Net.Entities.CustomFields; using Moneybird.Net.Entities.SalesInvoices; using Moneybird.Net.Http; +using Moneybird.Net.Misc; using Moneybird.Net.Models.SalesInvoices; using Moq; using Xunit; @@ -23,6 +24,7 @@ public class SalesInvoiceEndpointTests : SalesInvoiceTestBase private const string GetSalesInvoicesResponsePath = "./Responses/Endpoints/SalesInvoices/getSalesInvoices.json"; private const string GetSalesInvoiceResponsePath = "./Responses/Endpoints/SalesInvoices/getSalesInvoice.json"; private const string PostSalesInvoiceResponsePath = "./Responses/Endpoints/SalesInvoices/postSalesInvoice.json"; + private const string SendSalesInvoiceResponsePath = "./Responses/Endpoints/SalesInvoices/sendSalesInvoice.json"; public SalesInvoiceEndpointTests() { @@ -267,6 +269,36 @@ public async void DeleteSalesInvoiceAsync_ByAccessToken_Returns_True() Assert.True(actualSalesInvoice); } + [Fact] + public async void SendSalesInvoiceAsync_ByAccessToken_Returns_UpdatedSalesInvoice() + { + var salesInvoiceJson = await File.ReadAllTextAsync(SendSalesInvoiceResponsePath); + var salesInvoiceSendOptions = new SalesInvoiceSendOptions + { + DeliveryMethod = DeliveryMethod.Email, + SendingScheduled = false, + DeliverUbl = false, + Mergeable = false, + EmailAddress = "info@example.com", + EmailMessage = "Geachte Foobar Holding B.V.,\n\nIn de bijlage kunt u factuur 2024-0001 voor onze diensten vinden. Wij verzoeken u vriendelijk de factuur voor 30-07-2024 te voldoen.\n\nMet vriendelijke groet,\n\nParkietje B.V.", + InvoiceDate = null + }; + + var serializedSalesInvoiceSendOptions = JsonSerializer.Serialize(salesInvoiceSendOptions, _config.SerializerOptions); + + _requester.Setup(moq => moq.CreatePatchRequestAsync(It.IsAny(), It.IsAny(), + It.IsAny(), It.Is(s => s.Equals(serializedSalesInvoiceSendOptions)), It.IsAny>())) + .ReturnsAsync(salesInvoiceJson); + + var salesInvoice = JsonSerializer.Deserialize(salesInvoiceJson, _config.SerializerOptions); + Assert.NotNull(salesInvoice); + + var actualSalesInvoice = await _salesInvoiceEndpoint.SendInvoice(AdministrationId, SalesInvoiceId, salesInvoiceSendOptions, AccessToken); + Assert.NotNull(actualSalesInvoice); + + salesInvoice.Should().BeEquivalentTo(actualSalesInvoice); + } + [Fact] public async void AddSalesInvoiceAttachmentAsync_ByAccessToken_Returns() { diff --git a/tests/Moneybird.Net.Tests/Moneybird.Net.Tests.csproj b/tests/Moneybird.Net.Tests/Moneybird.Net.Tests.csproj index b313085..fd60d9e 100644 --- a/tests/Moneybird.Net.Tests/Moneybird.Net.Tests.csproj +++ b/tests/Moneybird.Net.Tests/Moneybird.Net.Tests.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Moneybird.Net.Tests/Responses/Endpoints/SalesInvoices/sendSalesInvoice.json b/tests/Moneybird.Net.Tests/Responses/Endpoints/SalesInvoices/sendSalesInvoice.json new file mode 100644 index 0000000..790d50c --- /dev/null +++ b/tests/Moneybird.Net.Tests/Responses/Endpoints/SalesInvoices/sendSalesInvoice.json @@ -0,0 +1,187 @@ +{ + "id": "426664263209191199", + "administration_id": "281289699686381606", + "contact_id": "426664263157810972", + "contact": { + "id": "426664263157810972", + "administration_id": "281289699686381606", + "company_name": "Foobar Holding B.V.", + "firstname": "", + "lastname": "", + "address1": "Hoofdstraat 12", + "address2": "", + "zipcode": "1234AB", + "city": "Amsterdam", + "country": "NL", + "phone": "", + "delivery_method": "Email", + "customer_id": "1", + "tax_number": "", + "chamber_of_commerce": "", + "bank_account": "", + "attention": "", + "email": "info@example.com", + "email_ubl": true, + "send_invoices_to_attention": "", + "send_invoices_to_email": "info@example.com", + "send_estimates_to_attention": "", + "send_estimates_to_email": "info@example.com", + "sepa_active": false, + "sepa_iban": "", + "sepa_iban_account_name": "", + "sepa_bic": "", + "sepa_mandate_id": "", + "sepa_mandate_date": null, + "sepa_sequence_type": "RCUR", + "credit_card_number": "", + "credit_card_reference": "", + "credit_card_type": null, + "tax_number_validated_at": null, + "tax_number_valid": null, + "invoice_workflow_id": null, + "estimate_workflow_id": null, + "si_identifier": "", + "si_identifier_type": null, + "moneybird_payments_mandate": false, + "created_at": "2024-07-16T08:32:51.222Z", + "updated_at": "2024-07-16T08:32:51.222Z", + "version": 1721118771, + "sales_invoices_url": "https://moneybird.dev/123/sales_invoices/844adf6b1bc4707699d9dd99a1b98993098ad5de1e4879f7e3b9ae7ab4f19093/all", + "notes": [ + + ], + "custom_fields": [ + + ], + "contact_people": [ + + ], + "archived": false + }, + "contact_person_id": null, + "contact_person": null, + "invoice_id": "2024-0001", + "recurring_sales_invoice_id": null, + "subscription_id": null, + "workflow_id": "426664036392764982", + "document_style_id": "426664036528031296", + "identity_id": "426664036248061493", + "draft_id": null, + "state": "open", + "invoice_date": "2024-07-16", + "due_date": "2024-07-30", + "payment_conditions": "We verzoeken u vriendelijk het bovenstaande bedrag van {document.total_price} voor {document.due_date} te voldoen op onze bankrekening onder vermelding van de omschrijving {document.payment_reference}. Voor vragen kunt u contact opnemen per e-mail.", + "payment_reference": "RF020000000000000S42QAK2U", + "short_payment_reference": "RF02S42QAK2U", + "reference": "Project X", + "language": "nl", + "currency": "EUR", + "discount": "0.0", + "original_sales_invoice_id": null, + "paused": false, + "paid_at": null, + "sent_at": "2024-07-16", + "created_at": "2024-07-16T08:32:51.271Z", + "updated_at": "2024-07-16T08:32:51.413Z", + "public_view_code": "690819", + "public_view_code_expires_at": "2024-10-16T08:32:51.375Z", + "version": 1721118771, + "details": [ + { + "id": "426664263213385504", + "administration_id": "281289699686381606", + "tax_rate_id": "426664036220798511", + "ledger_account_id": "426664036164175387", + "project_id": null, + "product_id": null, + "amount": "1 x", + "amount_decimal": "1.0", + "description": "Project X", + "price": "300.0", + "period": null, + "row_order": 1, + "total_price_excl_tax_with_discount": "300.0", + "total_price_excl_tax_with_discount_base": "300.0", + "tax_report_reference": [ + "NL/1a" + ], + "mandatory_tax_text": null, + "created_at": "2024-07-16T08:32:51.275Z", + "updated_at": "2024-07-16T08:32:51.412Z" + } + ], + "payments": [ + + ], + "total_paid": "0.0", + "total_unpaid": "363.0", + "total_unpaid_base": "363.0", + "prices_are_incl_tax": false, + "total_price_excl_tax": "300.0", + "total_price_excl_tax_base": "300.0", + "total_price_incl_tax": "363.0", + "total_price_incl_tax_base": "363.0", + "total_discount": "0.0", + "marked_dubious_on": null, + "marked_uncollectible_on": null, + "reminder_count": 0, + "next_reminder": "2024-07-30", + "original_estimate_id": null, + "url": "http://moneybird.dev/123/sales_invoices/d0584001cffb56da9149dec560fabc41d697d79ab4bdd74569d9b5aa27872ddf/844adf6b1bc4707699d9dd99a1b98993098ad5de1e4879f7e3b9ae7ab4f19093", + "payment_url": "http://moneybird.dev/123/online_sales_invoices/d0584001cffb56da9149dec560fabc41d697d79ab4bdd74569d9b5aa27872ddf/844adf6b1bc4707699d9dd99a1b98993098ad5de1e4879f7e3b9ae7ab4f19093/pay_invoice", + "custom_fields": [ + + ], + "notes": [ + + ], + "attachments": [ + + ], + "events": [ + { + "administration_id": "281289699686381606", + "user_id": 17211185545521, + "action": "sales_invoice_created", + "link_entity_id": null, + "link_entity_type": null, + "data": { + }, + "created_at": "2024-07-16T08:32:51.279Z", + "updated_at": "2024-07-16T08:32:51.279Z" + }, + { + "administration_id": "281289699686381606", + "user_id": 17211185545521, + "action": "sales_invoice_state_changed_to_open", + "link_entity_id": null, + "link_entity_type": null, + "data": { + }, + "created_at": "2024-07-16T08:32:51.388Z", + "updated_at": "2024-07-16T08:32:51.388Z" + }, + { + "administration_id": "281289699686381606", + "user_id": 17211185545521, + "action": "sales_invoice_send_email", + "link_entity_id": null, + "link_entity_type": null, + "data": { + "email_address": "info@example.com", + "email_message": "Geachte Foobar Holding B.V.,\n\nIn de bijlage kunt u factuur 2024-0001 voor onze diensten vinden. Wij verzoeken u vriendelijk de factuur voor 30-07-2024 te voldoen.\n\nMet vriendelijke groet,\n\nParkietje B.V." + }, + "created_at": "2024-07-16T08:32:51.429Z", + "updated_at": "2024-07-16T08:32:51.429Z" + } + ], + "tax_totals": [ + { + "tax_rate_id": "426664036220798511", + "taxable_amount": "300.0", + "taxable_amount_base": "300.0", + "tax_amount": "63.0", + "tax_amount_base": "63.0" + } + ] +} \ No newline at end of file