1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-29 10:24:20 +01:00
fab-manager/app/services/accounting/accounting_service.rb
2023-07-03 17:43:27 +02:00

183 lines
6.4 KiB
Ruby

# frozen_string_literal: false
# module definition
module Accounting; end
# Provides the routine to build the accounting data and save them in DB
class Accounting::AccountingService
def initialize
@currency = ENV.fetch('INTL_CURRENCY') { '' }
@journal_service = Accounting::AccountingJournalService.new
end
# build accounting lines for invoices between the provided dates
def build(start_date, end_date)
invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC')
build_from_invoices(invoices)
invoices.map(&:id)
end
# build accounting lines for the provided invoices
def build_from_invoices(invoices)
lines = []
processed = []
invoices.find_each do |i|
Rails.logger.debug { "[AccountLine] processing invoice #{i.id}..." } unless Rails.env.test?
if i.main_item.nil?
Rails.logger.error { "[AccountLine] invoice #{i.id} main_item is nil" } unless Rails.env.test?
else
lines.concat(generate_lines(i))
processed.push(i.id)
end
end
ActiveRecord::Base.transaction do
ids = invoices.map(&:id)
AccountingLine.where(invoice_id: ids).delete_all
AccountingLine.create!(lines)
end
processed
end
def self.status
workers = Sidekiq::Workers.new
workers.each do |_process_id, _thread_id, work|
return 'building' if work['payload']['class'] == 'AccountingWorker'
end
'built'
end
private
def generate_lines(invoice)
lines = payment_lines(invoice) + items_lines(invoice)
vat = vat_line(invoice)
lines << vat unless vat.nil?
fix_rounding_errors(lines)
lines
end
# Generate the lines associated with the provided invoice, for the sales accounts
def items_lines(invoice)
lines = []
%w[Subscription Reservation WalletTransaction StatisticProfilePrepaidPack OrderItem Error].each do |object_type|
items = invoice.invoice_items.filter { |ii| ii.object_type == object_type }
items.each do |item|
lines << line(
invoice,
'item',
@journal_service.sales_journal(object_type),
Accounting::AccountingCodeService.sales_account(item),
item.net_amount,
account_label: Accounting::AccountingCodeService.sales_account(item, type: :label),
analytical_code: Accounting::AccountingCodeService.sales_account(item, section: :analytical_section)
)
end
end
lines
end
# Generate the "payment" lines, which contains the debit to the client account, all taxes included
def payment_lines(invoice)
lines = []
invoice.payment_means.each do |details|
lines << line(
invoice,
'payment',
@journal_service.payment_journal(details[:means]),
Accounting::AccountingCodeService.payment_account(details[:means]),
details[:amount],
account_label: Accounting::AccountingCodeService.payment_account(details[:means], type: :label),
debit_method: :debit_client,
credit_method: :credit_client
)
end
lines
end
# Generate the "VAT" line, which contains the credit to the VAT account, with total VAT amount only
def vat_line(invoice)
vat_rate_groups = VatHistoryService.new.invoice_vat(invoice)
total_vat = vat_rate_groups.values.pluck(:total_vat).sum
# we do not render the VAT row if it was disabled for this invoice
return nil if total_vat.zero?
line(
invoice,
'vat',
@journal_service.vat_journal,
Accounting::AccountingCodeService.vat_account,
total_vat,
account_label: Accounting::AccountingCodeService.vat_account(type: :label)
)
end
# Generate a row of the export, filling the configured columns with the provided values
def line(invoice, line_type, journal_code, account_code, amount,
account_label: '', analytical_code: '', debit_method: :debit, credit_method: :credit)
{
line_type: line_type,
journal_code: journal_code,
date: invoice.created_at,
account_code: account_code,
account_label: account_label,
analytical_code: analytical_code,
invoice_id: invoice.id,
invoicing_profile_id: invoice.invoicing_profile_id,
debit: method(debit_method).call(invoice, amount),
credit: method(credit_method).call(invoice, amount),
currency: @currency,
summary: summary(invoice)
}
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)
invoice.is_a?(Avoir) ? amount : 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)
invoice.is_a?(Avoir) ? 0 : amount
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
# Create a text from the given invoice, matching the accounting software rules for the labels
def summary(invoice)
reference = invoice.reference
items = invoice.subscription_invoice? ? [I18n.t('accounting_summary.subscription_abbreviation')] : []
if invoice.main_item.object_type == 'Reservation'
items.push I18n.t("accounting_summary.#{invoice.main_item.object.reservable_type}_reservation_abbreviation")
end
items.push I18n.t('accounting_summary.wallet_abbreviation') if invoice.main_item.object_type == 'WalletTransaction'
items.push I18n.t('accounting_summary.shop_order_abbreviation') if invoice.main_item.object_type == 'OrderItem'
"#{reference}, #{items.join(' + ')}"
end
# In case of rounding errors, fix the balance by adding or removing a cent to the last item line
# This case should only happen when a coupon has been used.
def fix_rounding_errors(lines)
debit_sum = lines.filter { |l| l[:line_type] == 'payment' }.pluck(:debit).sum
credit_sum = lines.filter { |l| l[:line_type] != 'payment' }.pluck(:credit).sum
return if debit_sum == credit_sum
diff = debit_sum - credit_sum
fixable_line = lines.filter { |l| l[:line_type] == 'payment' }.last
fixable_line[:credit] += diff
end
end