# frozen_string_literal: false # Provides the routine to export the accounting data to an external accounting software class AccountingExportService attr_reader :encoding, :format, :separator, :journal_code, :date_format, :columns, :vat_service def initialize(columns, encoding = 'UTF-8', format = 'CSV', separator = ';', date_format = '%d/%m/%Y') @encoding = encoding @format = format @separator = separator @journal_code = Setting.find_by(name: 'accounting_journal_code')&.value || '' @date_format = date_format @columns = columns @vat_service = VatHistoryService.new end def export(start_date, end_date, file) # build CVS content content = header_row invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC') invoices.each do |i| content << generate_rows(i) end # write content to file File.open(file, "w:#{encoding}") { |f| f.puts content.encode(encoding, invalid: :replace, undef: :replace) } end private def header_row row = '' columns.each do |column| row << I18n.t("accounting_export.#{column}") << separator end "#{row}\n" end def generate_rows(invoice) "#{client_row(invoice)}\n" \ "#{items_rows(invoice)}" \ "#{vat_row(invoice)}\n" end # Generate the "subscription" and "reservation" rows associated with the provided invoice def items_rows(invoice) rows = invoice.subscription_invoice? ? "#{subscription_row(invoice)}\n" : '' if invoice.invoiced_type == 'Reservation' invoice.invoice_items.each do |item| rows << "#{reservation_row(invoice, item)}\n" end end rows end # Generate the "client" row, which contains the debit to the client account, all taxes included def client_row(invoice) total = invoice.total / 100.00 row = '' columns.each do |column| case column when 'journal_code' row << journal_code when 'date' row << invoice.created_at&.strftime(date_format) when 'account_code' row << account(invoice, :client) when 'account_label' row << account(invoice, :client, :label) when 'piece' row << invoice.reference when 'line_label' row << label(invoice.invoicing_profile.full_name) when 'debit_origin' row << debit_client(invoice, total) when 'credit_origin' row << credit_client(invoice, total) when 'debit_euro' row << debit_client(invoice, total) when 'credit_euro' row << credit_client(invoice, total) when 'lettering' row << '' else puts "Unsupported column: #{column}" end row << separator end row end # Generate the "reservation" row, which contains the credit to the reservation account, all taxes excluded def reservation_row(invoice, item) wo_taxes = (item.amount / (vat_service.invoice_vat(invoice) / 100.00 + 1)) / 100.00 row = '' columns.each do |column| case column when 'journal_code' row << journal_code when 'date' row << invoice.created_at&.strftime(date_format) when 'account_code' row << account(invoice, :reservation) when 'account_label' row << account(invoice, :reservation, :label) when 'piece' row << invoice.reference when 'line_label' row << label(item.description) when 'debit_origin' row << debit(invoice, wo_taxes) when 'credit_origin' row << credit(invoice, wo_taxes) when 'debit_euro' row << debit(invoice, wo_taxes) when 'credit_euro' row << credit(invoice, wo_taxes) when 'lettering' row << '' else puts "Unsupported column: #{column}" end row << separator end row end # Generate the "subscription" row, which contains the credit to the subscription account, all taxes excluded def subscription_row(invoice) subscription_item = invoice.invoice_items.select(&:subscription).first wo_taxes = (subscription_item.amount / (vat_service.invoice_vat(invoice) / 100.00 + 1)) / 100.00 row = '' columns.each do |column| case column when 'journal_code' row << journal_code when 'date' row << invoice.created_at&.strftime(date_format) when 'account_code' row << account(invoice, :subscription) when 'account_label' row << account(invoice, :subscription, :label) when 'piece' row << invoice.reference when 'line_label' row << label(subscription_item.description) when 'debit_origin' row << debit(invoice, wo_taxes) when 'credit_origin' row << credit(invoice, wo_taxes) when 'debit_euro' row << debit(invoice, wo_taxes) when 'credit_euro' row << credit(invoice, wo_taxes) when 'lettering' row << '' else puts "Unsupported column: #{column}" end row << separator end row end # Generate the "VAT" row, which contains the credit to the VAT account, with VAT amount only def vat_row(invoice) vat = (invoice.total - (invoice.total / (vat_service.invoice_vat(invoice) / 100.00 + 1))) / 100.00 row = '' columns.each do |column| case column when 'journal_code' row << journal_code when 'date' row << invoice.created_at&.strftime(date_format) when 'account_code' row << account(invoice, :vat) when 'account_label' row << account(invoice, :vat, :label) when 'piece' row << invoice.reference when 'line_label' row << I18n.t('accounting_export.VAT') when 'debit_origin' row << debit(invoice, vat) when 'credit_origin' row << credit(invoice, vat) when 'debit_euro' row << debit(invoice, vat) when 'credit_euro' row << credit(invoice, vat) when 'lettering' row << '' else puts "Unsupported column: #{column}" end row << separator end row end # Get the account code (or label) for the given invoice and the specified line type (client, vat, subscription or reservation) def account(invoice, account, type = :code) res = case account when :client Setting.find_by(name: "accounting_client_#{type}")&.value when :vat Setting.find_by(name: "accounting_VAT_#{type}")&.value when :subscription if invoice.subscription_invoice? Setting.find_by(name: "accounting_subscription_#{type}")&.value else puts "WARN: Invoice #{invoice.id} has no subscription" end when :reservation if invoice.invoiced_type == 'Reservation' Setting.find_by(name: "accounting_#{invoice.invoiced.reservable_type}_#{type}")&.value else puts "WARN: Invoice #{invoice.id} has no reservation" end else puts "Unsupported account #{account}" end res || '' end # Fill the value of the "debit" column: if the invoice is a refund, returns the given amount, returns 0 otherwise def debit(invoice, amount) avoir = invoice.is_a? Avoir avoir ? amount.to_s : '0' end # Fill the value of the "credit" column: if the invoice is a refund, returns 0, otherwise, returns the given amount def credit(invoice, amount) avoir = invoice.is_a? Avoir avoir ? '0' : amount.to_s end # Fill the value of the "debit" column for the client row: if the invoice is a refund, returns 0, otherwise, returns the given amount def debit_client(invoice, amount) credit(invoice, amount) end # Fill the value of the "credit" column, for the client row: if the invoice is a refund, returns the given amount, returns 0 otherwise def credit_client(invoice, amount) debit(invoice, amount) end # Format the given text to match the accounting software rules for the labels def label(text) res = text.tr separator, '' res.truncate(50) end end