1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00
fab-manager/app/services/accounting_export_service.rb

294 lines
9.7 KiB
Ruby
Raw Normal View History

# frozen_string_literal: false
# Provides the routine to export the accounting data to an external accounting software
class AccountingExportService
include ActionView::Helpers::NumberHelper
attr_reader :encoding, :format, :separator, :journal_code, :date_format, :columns, :vat_service, :decimal_separator, :label_max_length,
:export_zeros
def initialize(columns, encoding: 'UTF-8', format: 'CSV', separator: ';')
@encoding = encoding
@format = format
@separator = separator
@decimal_separator = ','
@date_format = '%d/%m/%Y'
@label_max_length = 50
@export_zeros = false
@journal_code = Setting.find_by(name: 'accounting_journal_code')&.value || ''
@date_format = date_format
@columns = columns
@vat_service = VatHistoryService.new
end
def set_options(decimal_separator: ',', date_format: '%d/%m/%Y', label_max_length: 50, export_zeros: false)
@decimal_separator = decimal_separator
@date_format = date_format
@label_max_length = label_max_length
@export_zeros = export_zeros
end
def export(start_date, end_date, file)
# build CVS content
2019-08-01 09:49:09 +02:00
content = header_row
invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC')
invoices = invoices.where('total > 0') unless export_zeros
invoices.each do |i|
2019-08-01 09:49:09 +02:00
content << generate_rows(i)
end
# write content to file
2019-08-01 09:49:09 +02:00
File.open(file, "w:#{encoding}") { |f| f.puts content.encode(encoding, invalid: :replace, undef: :replace) }
end
private
2019-08-01 09:49:09 +02:00
def header_row
row = ''
columns.each do |column|
row << I18n.t("accounting_export.#{column}") << separator
end
"#{row}\n"
2019-08-01 09:49:09 +02:00
end
def generate_rows(invoice)
vat = vat_row(invoice)
2019-08-01 09:49:09 +02:00
"#{client_row(invoice)}\n" \
"#{items_rows(invoice)}" \
"#{vat.nil? ? '' : "#{vat}\n"}"
end
# Generate the "subscription" and "reservation" rows associated with the provided invoice
def items_rows(invoice)
2019-08-01 09:49:09 +02:00
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
2019-08-01 09:49:09 +02:00
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'
2019-08-01 09:49:09 +02:00
row << journal_code
when 'date'
2019-08-01 09:49:09 +02:00
row << invoice.created_at&.strftime(date_format)
when 'account_code'
2019-08-01 09:49:09 +02:00
row << account(invoice, :client)
when 'account_label'
2019-08-01 09:49:09 +02:00
row << account(invoice, :client, :label)
when 'piece'
2019-08-01 09:49:09 +02:00
row << invoice.reference
when 'line_label'
row << label(invoice)
when 'debit_origin'
2019-08-01 09:49:09 +02:00
row << debit_client(invoice, total)
when 'credit_origin'
2019-08-01 09:49:09 +02:00
row << credit_client(invoice, total)
when 'debit_euro'
2019-08-01 09:49:09 +02:00
row << debit_client(invoice, total)
when 'credit_euro'
2019-08-01 09:49:09 +02:00
row << credit_client(invoice, total)
when 'lettering'
row << ''
else
puts "Unsupported column: #{column}"
end
2019-08-01 09:49:09 +02:00
row << separator
end
2019-08-01 09:49:09 +02:00
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
2019-09-17 17:39:05 +02:00
wo_taxes_coupon = amount_after_coupon(invoice, wo_taxes)
row = ''
columns.each do |column|
case column
when 'journal_code'
2019-08-01 09:49:09 +02:00
row << journal_code
when 'date'
2019-08-01 09:49:09 +02:00
row << invoice.created_at&.strftime(date_format)
when 'account_code'
2019-08-01 09:49:09 +02:00
row << account(invoice, :reservation)
when 'account_label'
2019-08-01 09:49:09 +02:00
row << account(invoice, :reservation, :label)
when 'piece'
2019-08-01 09:49:09 +02:00
row << invoice.reference
when 'line_label'
row << ''
when 'debit_origin'
2019-09-17 17:39:05 +02:00
row << debit(invoice, wo_taxes_coupon)
when 'credit_origin'
2019-09-17 17:39:05 +02:00
row << credit(invoice, wo_taxes_coupon)
when 'debit_euro'
2019-09-17 17:39:05 +02:00
row << debit(invoice, wo_taxes_coupon)
when 'credit_euro'
2019-09-17 17:39:05 +02:00
row << credit(invoice, wo_taxes_coupon)
2019-08-01 09:49:09 +02:00
when 'lettering'
row << ''
else
puts "Unsupported column: #{column}"
end
2019-08-01 09:49:09 +02:00
row << separator
end
2019-08-01 09:49:09 +02:00
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
2019-09-17 17:39:05 +02:00
wo_taxes_coupon = amount_after_coupon(invoice, wo_taxes)
row = ''
columns.each do |column|
case column
when 'journal_code'
2019-08-01 09:49:09 +02:00
row << journal_code
when 'date'
2019-08-01 09:49:09 +02:00
row << invoice.created_at&.strftime(date_format)
when 'account_code'
2019-08-01 09:49:09 +02:00
row << account(invoice, :subscription)
when 'account_label'
2019-08-01 09:49:09 +02:00
row << account(invoice, :subscription, :label)
when 'piece'
2019-08-01 09:49:09 +02:00
row << invoice.reference
when 'line_label'
row << ''
when 'debit_origin'
2019-09-17 17:39:05 +02:00
row << debit(invoice, wo_taxes_coupon)
when 'credit_origin'
2019-09-17 17:39:05 +02:00
row << credit(invoice, wo_taxes_coupon)
when 'debit_euro'
2019-09-17 17:39:05 +02:00
row << debit(invoice, wo_taxes_coupon)
when 'credit_euro'
2019-09-17 17:39:05 +02:00
row << credit(invoice, wo_taxes_coupon)
2019-08-01 09:49:09 +02:00
when 'lettering'
row << ''
else
puts "Unsupported column: #{column}"
end
2019-08-01 09:49:09 +02:00
row << separator
end
2019-08-01 09:49:09 +02:00
row
end
2019-07-30 10:27:47 +02:00
# Generate the "VAT" row, which contains the credit to the VAT account, with VAT amount only
def vat_row(invoice)
rate = vat_service.invoice_vat(invoice)
# we do not render the VAT row if it was disabled for this invoice
return nil if rate.zero?
2019-09-18 15:09:14 +02:00
# FIXME, +/-0.01
vat = (invoice.total - (invoice.total / (rate / 100.00 + 1))) / 100.00
row = ''
columns.each do |column|
case column
when 'journal_code'
2019-08-01 09:49:09 +02:00
row << journal_code
when 'date'
2019-08-01 09:49:09 +02:00
row << invoice.created_at&.strftime(date_format)
when 'account_code'
2019-08-01 09:49:09 +02:00
row << account(invoice, :vat)
when 'account_label'
2019-08-01 09:49:09 +02:00
row << account(invoice, :vat, :label)
when 'piece'
2019-08-01 09:49:09 +02:00
row << invoice.reference
when 'line_label'
row << ''
when 'debit_origin'
2019-08-01 09:49:09 +02:00
row << debit(invoice, vat)
when 'credit_origin'
2019-08-01 09:49:09 +02:00
row << credit(invoice, vat)
when 'debit_euro'
2019-08-01 09:49:09 +02:00
row << debit(invoice, vat)
when 'credit_euro'
2019-08-01 09:49:09 +02:00
row << credit(invoice, vat)
when 'lettering'
row << ''
else
puts "Unsupported column: #{column}"
end
2019-08-01 09:49:09 +02:00
row << separator
end
2019-08-01 09:49:09 +02:00
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)
2019-08-01 09:49:09 +02:00
res = case account
when :client
means = invoice.paid_with_stripe? ? 'card' : 'site'
Setting.find_by(name: "accounting_#{means}_client_#{type}")&.value
2019-08-01 09:49:09 +02:00
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'
2019-09-18 15:09:14 +02:00
Setting.find_by(name: "accounting_#{invoice.invoiced.reservable_type}_#{type}")&.value
2019-08-01 09:49:09 +02:00
else
puts "WARN: Invoice #{invoice.id} has no reservation"
end
else
puts "Unsupported account #{account}"
end
res || ''
end
2019-07-30 10:27:47 +02:00
# 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 ? format_number(amount) : '0'
2019-07-30 10:27:47 +02:00
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' : format_number(amount)
2019-07-30 10:27:47 +02:00
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
2019-09-17 17:39:05 +02:00
# Return the given amount, after the coupon attached to the given invoice was applied, if any
def amount_after_coupon(invoice, amount)
cs = CouponService.new
invoice.coupon_id.nil? ? amount : cs.ventilate(cs.invoice_total_no_coupon(invoice), amount, invoice.coupon)
end
# Format the given number as a string, using the configured separator
def format_number(num)
number_to_currency(num, unit: '', separator: decimal_separator, delimiter: '', precision: 2)
end
# Create a text from the given invoice, matching the accounting software rules for the labels
def label(invoice)
name = "#{invoice.invoicing_profile.last_name} #{invoice.invoicing_profile.first_name}".tr separator, ''
reference = invoice.reference
items = invoice.subscription_invoice? ? [I18n.t('accounting_export.subscription')] : []
items.push I18n.t("accounting_export.#{invoice.reservation.reservable_type}_reservation") if invoice.invoiced_type == 'Reservation'
summary = items.join(' + ')
res = "#{reference}, #{summary}"
"#{name.truncate(label_max_length - res.length)}, #{res}"
end
end