From 6eb5f3ca63cecb13147a123b41795d44b1d6ebb7 Mon Sep 17 00:00:00 2001 From: Diego Steiner Date: Wed, 27 Dec 2023 18:42:53 +0000 Subject: [PATCH 1/4] feature: add support for camt.053 and camt.054 --- app/controllers/manage/payments_controller.rb | 2 +- app/models/payment/factory.rb | 38 -------------- app/services/camt_service.rb | 50 +++++++++++++++++++ config/initializers/camt_parser.rb | 3 ++ 4 files changed, 54 insertions(+), 39 deletions(-) create mode 100644 app/services/camt_service.rb create mode 100644 config/initializers/camt_parser.rb diff --git a/app/controllers/manage/payments_controller.rb b/app/controllers/manage/payments_controller.rb index 1400b41e..52b3d6f0 100644 --- a/app/controllers/manage/payments_controller.rb +++ b/app/controllers/manage/payments_controller.rb @@ -38,7 +38,7 @@ def new_import @bookings = bookings_for_import @invoices = invoices_for_import @payments = params[:camt_file].presence && - Payment::Factory.new(current_organisation).from_camt_file(params[:camt_file]) + CamtService.new(current_organisation).payments_from_file(params[:camt_file]) render 'import' if @payments.present? rescue CamtParser::Errors::BaseError, Nokogiri::SyntaxError => e diff --git a/app/models/payment/factory.rb b/app/models/payment/factory.rb index fb0bae4b..c01be4e7 100644 --- a/app/models/payment/factory.rb +++ b/app/models/payment/factory.rb @@ -6,44 +6,6 @@ def initialize(organisation) @organisation = organisation end - def from_camt_file(file) - camt = CamtParser::String.parse file.read - camt.notifications.map do |notification| - notification.entries.flat_map { |entry| from_camt_entry(entry) } - end.flatten.compact - end - - def from_camt_entry(entry) - return unless entry.booked? && entry.credit? && entry.currency.upcase == @organisation.currency.upcase - - entry.transactions.map do |transaction| - from_camt_transaction(transaction, entry) - end - end - - def find_invoice_by_ref(ref) - @organisation.invoice_ref_strategy.find_invoice_by_ref(ref, scope: @organisation.invoices.kept) - end - - def from_camt_transaction(transaction, entry) - ref = transaction.creditor_reference - invoice = find_invoice_by_ref(ref) - remarks = [transaction.name, entry.description].compact_blank.join("\n\n") - - Payment.new( - invoice:, booking: invoice&.booking, applies: invoice.present?, ref:, - paid_at: entry.value_date, amount: transaction.amount, data: camt_transaction_to_h(transaction), - camt_instr_id: transaction.reference, remarks: - ) - end - - def camt_transaction_to_h(transaction) - fields = %i[amount amount_in_cents currency name iban bic debit sign - remittance_information swift_code reference bank_reference end_to_end_reference mandate_reference - creditor_reference transaction_id creditor_identifier payment_information additional_information] - fields.index_with { |field| transaction.try(field) } - end - def from_import(payments_params) payments = payments_params.values.filter_map { |payment_params| Payment.new(payment_params) } payments = payments.select(&:applies) diff --git a/app/services/camt_service.rb b/app/services/camt_service.rb new file mode 100644 index 00000000..6c78f407 --- /dev/null +++ b/app/services/camt_service.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class CamtService + def initialize(organisation) + @organisation = organisation + end + + def payments_from_file(file) # rubocop:disable Metrics/CyclomaticComplexity + camt = CamtParser::String.parse file.read + entries = case camt + when CamtParser::Format053::Base + camt.statements.flat_map(&:entries) + when CamtParser::Format054::Base + camt.notifications.flat_map(&:entries) + end + + entries&.flat_map { |entry| payments_from_entry(entry) }&.compact + end + + def payments_from_entry(entry) + return unless entry.booked? && entry.credit? && entry.currency.upcase == @organisation.currency.upcase + + entry.transactions.map do |transaction| + payment_from_transaction(transaction, entry) + end + end + + def payment_from_transaction(transaction, entry) + ref = transaction.creditor_reference + invoice = find_invoice_by_ref(ref) + remarks = [transaction.name, entry.description].compact_blank.join("\n\n") + + Payment.new( + invoice:, booking: invoice&.booking, applies: invoice.present?, ref:, + paid_at: entry.value_date, amount: transaction.amount, data: transaction_to_h(transaction), + camt_instr_id: transaction.reference, remarks: + ) + end + + def find_invoice_by_ref(ref) + @organisation.invoice_ref_strategy.find_invoice_by_ref(ref, scope: @organisation.invoices.kept) + end + + def transaction_to_h(transaction) + fields = %i[amount amount_in_cents currency name iban bic debit sign + remittance_information swift_code reference bank_reference end_to_end_reference mandate_reference + creditor_reference transaction_id creditor_identifier payment_information additional_information] + fields.index_with { |field| transaction.try(field) } + end +end diff --git a/config/initializers/camt_parser.rb b/config/initializers/camt_parser.rb new file mode 100644 index 00000000..8ac5f531 --- /dev/null +++ b/config/initializers/camt_parser.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +CamtParser::Xml.register('urn:iso:std:iso:20022:tech:xsd:camt.054.001.08', :camt054) From efb6ce1c4d7ea5336c3085b966b5a48cf480dc17 Mon Sep 17 00:00:00 2001 From: Diego Steiner Date: Thu, 28 Dec 2023 09:40:46 +0000 Subject: [PATCH 2/4] fix: payable until refactoring --- app/models/invoice/factory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/invoice/factory.rb b/app/models/invoice/factory.rb index 3f9a5ad0..c3cc49cf 100644 --- a/app/models/invoice/factory.rb +++ b/app/models/invoice/factory.rb @@ -65,7 +65,7 @@ def payable_until(invoice) settings = invoice.booking.organisation.settings if invoice.is_a?(Invoices::Deposit) return [settings.deposit_payment_deadline.from_now, - invoice.booking.begins_at] + invoice.booking.begins_at].min end settings.invoice_payment_deadline.from_now From faa38b6f789edbf04c0a02be3399c875c9432853 Mon Sep 17 00:00:00 2001 From: Diego Steiner Date: Thu, 28 Dec 2023 11:27:19 +0000 Subject: [PATCH 3/4] feature: count payments without invoices as deposits --- app/models/invoice_part/factory.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/models/invoice_part/factory.rb b/app/models/invoice_part/factory.rb index f52ea74e..d94ee657 100644 --- a/app/models/invoice_part/factory.rb +++ b/app/models/invoice_part/factory.rb @@ -13,6 +13,7 @@ def initialize(invoice) def call I18n.with_locale(invoice.locale || I18n.locale) do [ + from_payments.presence, from_deposits.presence, from_supersede_invoice.presence, from_usages.presence @@ -32,6 +33,17 @@ def from_usages(usages = booking.usages.ordered.where.not(id: invoice.invoice_pa end.flatten end + def from_payments + payed_amount = @invoice.booking.payments.where(invoice: nil, write_off: false).sum(:amount) + return [] unless payed_amount.positive? && @invoice.new_record? + + [ + InvoiceParts::Text.new(apply: suggest?, label: Invoices::Deposit.model_name.human), + InvoiceParts::Add.new(apply: suggest?, label: I18n.t('invoice_parts.deposited_amount'), + amount: - payed_amount) + ] + end + def from_deposits deposits = Invoices::Deposit.of(@invoice.booking).kept deposited_amount = deposits.sum(&:amount_paid) From 891a118aff646848d63e21ac3bd769cab361f0c5 Mon Sep 17 00:00:00 2001 From: Diego Steiner Date: Thu, 28 Dec 2023 11:31:08 +0000 Subject: [PATCH 4/4] chore: bump version --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1df39167..968366ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## Version 23.12.2 + +Released on 28.12.2023 + +- Feature: Add column_config for data digests +- Feature: Improve deposit detection +- Bugfixes + ## Version 23.12.1 Released on 19.12.2023 diff --git a/VERSION b/VERSION index 62dacea7..45b2a115 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -V23.12.1 +V23.12.2