mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-29 10:24:20 +01:00
183 lines
6.4 KiB
Ruby
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
|