From ca7671b5721ca5954f7364b7a9b009d3b0993d9b Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 30 Dec 2025 14:45:03 +0200 Subject: [PATCH 1/3] Add e-invoice opt-out for PDF invoices Allow registrars who accept e-invoices to opt out of receiving additional PDF invoice emails. This reduces duplicate work when e-invoices are already processed directly by accounting. - Add column to registrars table (default: true) - Check e-invoice recipient status from Estonian Business Registry - Skip PDF email when registrar accepts e-invoices AND opts out - Expose setting via REPP API (GET details / PUT update) Closes #2881 --- Gemfile | 2 +- Gemfile.lock | 4 +- .../repp/v1/accounts_controller.rb | 3 +- app/models/contact/company_register.rb | 28 +++++++++- app/models/registrar.rb | 16 +++++- .../20251230104312_accept_pdf_invoices.rb | 5 ++ db/structure.sql | 21 +++++-- .../invoice_email_cancellation_test.rb | 55 +++++++++++++++++++ test/test_helper.rb | 9 +++ 9 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 db/migrate/20251230104312_accept_pdf_invoices.rb create mode 100644 test/models/registrar/invoice_email_cancellation_test.rb diff --git a/Gemfile b/Gemfile index 124b3dc09b..4d204c530f 100644 --- a/Gemfile +++ b/Gemfile @@ -62,7 +62,7 @@ gem 'omniauth-tara', github: 'internetee/omniauth-tara' # gem 'omniauth-tara', path: 'vendor/gems/omniauth-tara' gem 'airbrake' -gem 'company_register', github: 'internetee/company_register', branch: :master +gem 'company_register', github: 'internetee/company_register', branch: 'e-invoice-recipient' gem 'directo', github: 'internetee/directo', branch: 'master' gem 'domain_name' gem 'dry-struct' diff --git a/Gemfile.lock b/Gemfile.lock index 8766da290d..5004a3a340 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/internetee/company_register.git - revision: 2c31da54c57db13324161eeb8db7e9f81af81987 - branch: master + revision: ee8d63cc26174d7ba663817ede6173ec173486f5 + branch: e-invoice-recipient specs: company_register (0.1.0) activesupport diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb index 2fe716e9c9..9bc15d3672 100644 --- a/app/controllers/repp/v1/accounts_controller.rb +++ b/app/controllers/repp/v1/accounts_controller.rb @@ -38,6 +38,7 @@ def details api_users: serialized_users(current_user.api_users), white_ips: serialized_ips(registrar.white_ips), balance_auto_reload: type, + accept_pdf_invoices: registrar.accept_pdf_invoices, min_deposit: Setting.minimum_deposit }, roles: ApiUser::ROLES, interfaces: WhiteIp::INTERFACES } @@ -117,7 +118,7 @@ def balance private def account_params - params.require(:account).permit(:billing_email, :iban, :new_user_id) + params.require(:account).permit(:billing_email, :iban, :new_user_id, :accept_pdf_invoices) end def index_params diff --git a/app/models/contact/company_register.rb b/app/models/contact/company_register.rb index e0d2e822fe..894f395441 100644 --- a/app/models/contact/company_register.rb +++ b/app/models/contact/company_register.rb @@ -13,7 +13,7 @@ def return_company_status end def return_company_data - return unless org? + return unless is_contact_estonian_org? company_register.simple_data(registration_number: ident.to_s) rescue CompanyRegister::NotAvailableError @@ -21,13 +21,35 @@ def return_company_data end def return_company_details - return unless org? - + return unless is_contact_estonian_org? + company_register.company_details(registration_number: ident.to_s) rescue CompanyRegister::NotAvailableError [] end + def e_invoice_recipients + return unless is_contact_estonian_org? + + company_register.e_invoice_recipients(registration_numbers: ident.to_s) + rescue CompanyRegister::SOAPFaultError => e + Rails.logger.error("SOAP Fault getting company details for #{ident}: #{e.message}") + raise e + rescue CompanyRegister::NotAvailableError + [] + end + + def org_contact_accept_e_invoice? + return unless is_contact_estonian_org? + + result = e_invoice_recipients.first + result.status == 'OK' + end + + def is_contact_estonian_org? + org? && country_code == 'EE' + end + def company_register @company_register ||= CompanyRegister::Client.new end diff --git a/app/models/registrar.rb b/app/models/registrar.rb index 659ec7a370..b114ef6ca3 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -151,7 +151,7 @@ def issue_prepayment_invoice(amount, description = nil, payable: true) ] ) - unless payable + unless payable && (accepts_e_invoices? && !accept_pdf_invoices?) InvoiceMailer.invoice_email(invoice: invoice, recipient: billing_email, paid: !payable) .deliver_later(wait: 1.minute) end @@ -307,6 +307,20 @@ def billing_email self[:billing_email] end + def accepts_e_invoices? + return false unless address_country_code == 'EE' + + result = company_register.e_invoice_recipients(registration_numbers: reg_no).first + result.status == 'OK' + rescue CompanyRegister::NotAvailableError, CompanyRegister::SOAPFaultError => e + Rails.logger.error("Error checking e-invoice status for #{reg_no}: #{e.message}") + false + end + + def company_register + @company_register ||= CompanyRegister::Client.new + end + private def domain_not_updatable?(hostname:, domain:) diff --git a/db/migrate/20251230104312_accept_pdf_invoices.rb b/db/migrate/20251230104312_accept_pdf_invoices.rb new file mode 100644 index 0000000000..ba22f4325e --- /dev/null +++ b/db/migrate/20251230104312_accept_pdf_invoices.rb @@ -0,0 +1,5 @@ +class AcceptPdfInvoices < ActiveRecord::Migration[6.1] + def change + add_column :registrars, :accept_pdf_invoices, :boolean, default: true + end +end diff --git a/db/structure.sql b/db/structure.sql index faa3014b0d..a22fbc44b7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1,3 +1,8 @@ +\restrict manNM7YktJCYjflsdAWd12PkDkR5VB5nDAhRVyBVVHwcDScSXNtcviWOHPpPk1Q + +-- Dumped from database version 13.4 (Debian 13.4-4.pgdg110+1) +-- Dumped by pg_dump version 13.22 (Debian 13.22-0+deb11u1) + SET statement_timeout = 0; SET lock_timeout = 0; SET idle_in_transaction_session_timeout = 0; @@ -2641,7 +2646,8 @@ CREATE TABLE public.registrars ( settings jsonb DEFAULT '{}'::jsonb NOT NULL, legaldoc_optout boolean DEFAULT false NOT NULL, legaldoc_optout_comment text, - email_history character varying + email_history character varying, + accept_pdf_invoices boolean DEFAULT true ); @@ -5279,6 +5285,8 @@ ALTER TABLE ONLY public.users -- PostgreSQL database dump complete -- +\unrestrict manNM7YktJCYjflsdAWd12PkDkR5VB5nDAhRVyBVVHwcDScSXNtcviWOHPpPk1Q + SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES @@ -5767,10 +5775,13 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230707084741'), ('20230710120154'), ('20230711083811'), +('20240722085530'), +('20240723110208'), ('20240816091049'), ('20240816092636'), ('20240924103554'), ('20241015071505'), +('20241022121525'), ('20241030095636'), ('20241104104620'), ('20241112093540'), @@ -5779,12 +5790,10 @@ INSERT INTO "schema_migrations" (version) VALUES ('20241206085817'), ('20250204094550'), ('20250219102811'), -('20250313122119'), -('20250319104749'), ('20250310133151'), +('20250313122119'), ('20250314133357'), -('20240722085530'), -('20240723110208'), -('20241022121525'); +('20250319104749'), +('20251230104312'); diff --git a/test/models/registrar/invoice_email_cancellation_test.rb b/test/models/registrar/invoice_email_cancellation_test.rb new file mode 100644 index 0000000000..5e3fb30f92 --- /dev/null +++ b/test/models/registrar/invoice_email_cancellation_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +class RegistrarInvoiceEmailCancellationTest < ActiveJob::TestCase + setup do + @registrar = registrars(:bestnames) + @registrar.update!(address_country_code: 'EE', reference_no: '1232', vat_rate: 24) + @invoice_params = 100 + + stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_number_generator"). + to_return(status: 200, body: "{\"invoice_number\":\"123456\"}", headers: {}) + + stub_request(:post, "https://eis_billing_system:3000/api/v1/invoice_generator/invoice_generator"). + to_return(status: 200, body: "{\"everypay_link\":\"http://link.test\"}", headers: {}) + + stub_request(:put, "https://registry:3000/eis_billing/e_invoice_response"). + to_return(status: 200, body: "{\"invoice_number\":\"123456\"}, {\"date\":\"#{Time.zone.now}\"}", headers: {}) + + stub_request(:post, "https://eis_billing_system:3000/api/v1/e_invoice/e_invoice"). + to_return(status: 200, body: "", headers: {}) + end + + def test_sends_email_when_registrar_does_not_accept_e_invoices_and_pdf_opt_in_is_true + # Name does not contain 'einvoice', so stub returns 'MR' (not OK) + @registrar.update!(name: 'simple-registrar', accept_pdf_invoices: true) + + assert_enqueued_jobs 1, only: ActionMailer::MailDeliveryJob do + @registrar.issue_prepayment_invoice(@invoice_params) + end + end + + def test_sends_email_when_registrar_does_not_accept_e_invoices_and_pdf_opt_in_is_false + # Name does not contain 'einvoice', so stub returns 'MR' (not OK) + @registrar.update!(name: 'simple-registrar', accept_pdf_invoices: false) + + assert_enqueued_jobs 1, only: ActionMailer::MailDeliveryJob do + @registrar.issue_prepayment_invoice(@invoice_params) + end + end + + def test_skips_email_when_registrar_accepts_e_invoices_and_pdf_opt_in_is_false + @registrar.update!(name: 'einvoice-registrar', accept_pdf_invoices: false) + + assert_enqueued_jobs 0, only: ActionMailer::MailDeliveryJob do + @registrar.issue_prepayment_invoice(@invoice_params) + end + end + + def test_sends_email_when_registrar_accepts_e_invoices_BUT_pdf_opt_in_is_true + @registrar.update!(name: 'einvoice-registrar', accept_pdf_invoices: true) + + assert_enqueued_jobs 1, only: ActionMailer::MailDeliveryJob do + @registrar.issue_prepayment_invoice(@invoice_params) + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 90c0170ec8..b80e69846e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -46,6 +46,15 @@ def simple_data(registration_number:) def company_details(registration_number:) [] end + + def e_invoice_recipients(registration_numbers:) + if ::Registrar.exists?(reg_no: registration_numbers) + registrar = ::Registrar.find_by(reg_no: registration_numbers) + status = registrar.name.include?('einvoice') ? 'OK' : 'MR' + return [Struct.new(:status).new(status)] + end + [Struct.new(:status).new('OK')] + end end CompanyRegister::Client = CompanyRegisterClientStub From f9821484307e20771c9123e1757c3c5024d927c1 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Tue, 30 Dec 2025 15:29:18 +0200 Subject: [PATCH 2/3] fixed tests --- app/models/contact/company_register.rb | 2 +- test/models/contact/company_register_test.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/contact/company_register.rb b/app/models/contact/company_register.rb index 894f395441..0497443f0d 100644 --- a/app/models/contact/company_register.rb +++ b/app/models/contact/company_register.rb @@ -47,7 +47,7 @@ def org_contact_accept_e_invoice? end def is_contact_estonian_org? - org? && country_code == 'EE' + org? && ident_country_code == 'EE' end def company_register diff --git a/test/models/contact/company_register_test.rb b/test/models/contact/company_register_test.rb index 7b43d4b6ed..1ef1b22675 100644 --- a/test/models/contact/company_register_test.rb +++ b/test/models/contact/company_register_test.rb @@ -5,6 +5,8 @@ class CompanyRegisterTest < ActiveSupport::TestCase def setup @acme_ltd = contacts(:acme_ltd) + @acme_ltd.update!(ident_country_code: 'EE', ident_type: 'org', ident: '12345678') + @john = contacts(:john) @company_register_stub = CompanyRegister::Client.new end From ec7db763cb6f6bbb6ae470f550bdec21f25e5178 Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Wed, 31 Dec 2025 10:33:19 +0200 Subject: [PATCH 3/3] updated Gemfile: set master branch for company register gem --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4d204c530f..f0b36dd951 100644 --- a/Gemfile +++ b/Gemfile @@ -62,7 +62,7 @@ gem 'omniauth-tara', github: 'internetee/omniauth-tara' # gem 'omniauth-tara', path: 'vendor/gems/omniauth-tara' gem 'airbrake' -gem 'company_register', github: 'internetee/company_register', branch: 'e-invoice-recipient' +gem 'company_register', github: 'internetee/company_register', branch: 'master' gem 'directo', github: 'internetee/directo', branch: 'master' gem 'domain_name' gem 'dry-struct' diff --git a/Gemfile.lock b/Gemfile.lock index 5004a3a340..fb8f09ca4d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/internetee/company_register.git - revision: ee8d63cc26174d7ba663817ede6173ec173486f5 - branch: e-invoice-recipient + revision: d83597c5cc06c1909637e56d937a5b3bcb6286c6 + branch: master specs: company_register (0.1.0) activesupport