1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-20 14:54:15 +01:00

(bug) invalid accounting export for store orders

This commit is contained in:
Sylvain 2022-10-26 14:32:19 +02:00
parent 6d448e0af2
commit fc2b52c2ca
8 changed files with 249 additions and 209 deletions

View File

@ -204,7 +204,7 @@ class Setting < ApplicationRecord
# @return {String|Boolean}
##
def self.get(name)
res = find_by(name: name)&.value
res = find_by('LOWER(name) = ? ', name.downcase)&.value
# handle boolean values
return true if res == 'true'

View File

@ -61,28 +61,21 @@ class AccountingExportService
# Generate the "subscription" and "reservation" rows associated with the provided invoice
def items_rows(invoice)
rows = invoice.subscription_invoice? ? "#{subscription_row(invoice)}\n" : ''
case invoice.main_item.object_type
when 'Reservation'
items = invoice.invoice_items.reject { |ii| ii.object_type == 'Subscription' }
rows = ''
{
subscription: 'Subscription', reservation: 'Reservation', wallet: 'WalletTransaction',
pack: 'StatisticProfilePrepaidPack', product: 'OrderItem', error: 'Error'
}.each do |type, object_type|
items = invoice.invoice_items.filter { |ii| ii.object_type == object_type }
items.each do |item|
rows << "#{reservation_row(invoice, item)}\n"
rows << "#{row(
invoice,
account(invoice, type),
account(invoice, type, type: :label),
item.net_amount / 100.00,
line_label: label(invoice)
)}\n"
end
when 'WalletTransaction'
rows << "#{wallet_row(invoice)}\n"
when 'StatisticProfilePrepaidPack'
rows << "#{pack_row(invoice)}\n"
when 'OrderItem'
rows << "#{product_row(invoice)}\n"
when 'Error'
items = invoice.invoice_items.reject { |ii| ii.object_type == 'Subscription' }
items.each do |item|
rows << "#{error_row(invoice, item)}\n"
end
when 'Subscription'
# do nothing, subscription was already handled by subscription_row
else
Rails.logger.warn { "Unknown main object type #{invoice.main_item.object_type}" }
end
rows
end
@ -93,8 +86,8 @@ class AccountingExportService
invoice.payment_means.each do |details|
rows << row(
invoice,
account(invoice, :projets, means: details[:means]),
account(invoice, :projets, means: details[:means], type: :label),
account(invoice, :client, means: details[:means]),
account(invoice, :client, means: details[:means], type: :label),
details[:amount] / 100.00,
line_label: label(invoice),
debit_method: :debit_client,
@ -105,61 +98,6 @@ class AccountingExportService
rows
end
# Generate the "reservation" row, which contains the credit to the reservation account, all taxes excluded
def reservation_row(invoice, item)
row(
invoice,
account(invoice, :reservation),
account(invoice, :reservation, type: :label),
item.net_amount / 100.00,
line_label: label(invoice)
)
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 { |ii| ii.object_type == 'Subscription' }.first
row(
invoice,
account(invoice, :subscription),
account(invoice, :subscription, type: :label),
subscription_item.net_amount / 100.00,
line_label: label(invoice)
)
end
# Generate the "wallet" row, which contains the credit to the wallet account, all taxes excluded
# This applies to wallet crediting, when an Avoir is generated at this time
def wallet_row(invoice)
row(
invoice,
account(invoice, :wallet),
account(invoice, :wallet, type: :label),
invoice.invoice_items.first.net_amount / 100.00,
line_label: label(invoice)
)
end
def pack_row(invoice)
row(
invoice,
account(invoice, :pack),
account(invoice, :pack, type: :label),
invoice.invoice_items.first.net_amount / 100.00,
line_label: label(invoice)
)
end
def product_row(invoice)
row(
invoice,
account(invoice, :product),
account(invoice, :product, type: :label),
invoice.invoice_items.first.net_amount / 100.00,
line_label: label(invoice)
)
end
# Generate the "VAT" row, which contains the credit to the VAT account, with VAT amount only
def vat_row(invoice)
total = invoice.invoice_items.map(&:net_amount).sum
@ -175,16 +113,6 @@ class AccountingExportService
)
end
def error_row(invoice, item)
row(
invoice,
account(invoice, :error),
account(invoice, :error, type: :label),
item.net_amount / 100.00,
line_label: label(invoice)
)
end
# Generate a row of the export, filling the configured columns with the provided values
def row(invoice, account_code, account_label, amount, line_label: '', debit_method: :debit, credit_method: :credit)
row = ''
@ -219,44 +147,12 @@ class AccountingExportService
# 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, means: :other)
case account
when :projets
when :client
Setting.get("accounting_#{means}_client_#{type}")
when :vat
Setting.get("accounting_VAT_#{type}")
when :subscription
if invoice.subscription_invoice?
Setting.get("accounting_subscription_#{type}")
else
Rails.logger.debug { "WARN: Invoice #{invoice.id} has no subscription" }
end
when :reservation
if invoice.main_item.object_type == 'Reservation'
Setting.get("accounting_#{invoice.main_item.object.reservable_type}_#{type}")
else
Rails.logger.debug { "WARN: Invoice #{invoice.id} has no reservation" }
end
when :wallet
if invoice.main_item.object_type == 'WalletTransaction'
Setting.get("accounting_wallet_#{type}")
else
Rails.logger.debug { "WARN: Invoice #{invoice.id} is not a wallet credit" }
end
when :pack
if invoice.main_item.object_type == 'StatisticProfilePrepaidPack'
Setting.get("accounting_Pack_#{type}")
else
Rails.logger.debug { "WARN: Invoice #{invoice.id} has no prepaid-pack" }
end
when :product
if invoice.main_item.object_type == 'OrderItem'
Setting.get("accounting_Product_#{type}")
else
Rails.logger.debug { "WARN: Invoice #{invoice.id} has no prepaid-pack" }
end
when :error
Setting.get("accounting_Error_#{type}")
Setting.get("accounting_#{invoice.main_item.object.reservable_type}_#{type}") if invoice.main_item.object_type == 'Reservation'
else
Rails.logger.debug { "Unsupported account #{account}" }
Setting.get("accounting_#{account}_#{type}")
end || ''
end
@ -297,6 +193,7 @@ class AccountingExportService
items.push I18n.t("accounting_export.#{invoice.main_item.object.reservable_type}_reservation")
end
items.push I18n.t('accounting_export.wallet') if invoice.main_item.object_type == 'WalletTransaction'
items.push I18n.t('accounting_export.shop_order') if invoice.main_item.object_type == 'OrderItem'
summary = items.join(' + ')
res = "#{reference}, #{summary}"

View File

@ -164,6 +164,7 @@ en:
Event_reservation: "event reserv."
Space_reservation: "space reserv."
wallet: "wallet"
shop_order: "shop order"
vat_export:
start_date: "Start date"
end_date: "End date"

View File

@ -35,3 +35,15 @@ cash2:
max_usages:
active: true
validity_per_user: once
twentyp:
id: 30
name: 20% test
code: REDUC20
percent_off: 20
valid_until:
max_usages:
active: true
created_at: '2021-06-18 14:53:54.770895'
updated_at: '2021-06-18 14:53:54.770895'
validity_per_user: forever
amount_off:

View File

@ -494,7 +494,7 @@ history_value_51:
history_value_52:
id: 52
setting_id: 52
value: '7062'
value: Machine reservation
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.872288'
footprint: 0ee671e962f92a3af95538fdd65a64ff410b208ab790c4e6f7b0a9f5a13dc1a2
@ -503,7 +503,7 @@ history_value_52:
history_value_53:
id: 53
setting_id: 53
value: Machine reservation
value: '7062'
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.904414'
footprint: ad09bac8c58754444a9970108d613aefaeceed540609688f5668c68ca5c03531
@ -512,7 +512,7 @@ history_value_53:
history_value_54:
id: 54
setting_id: 54
value: '7063'
value: Training reservation
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.930713'
footprint: 89f17add783f9977c4b13bc22f5776d0427c3d9af8c83206af57de7c72c153b0
@ -521,7 +521,7 @@ history_value_54:
history_value_55:
id: 55
setting_id: 55
value: Training reservation
value: '7063'
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.955492'
footprint: f2e20f936e3fc917bc82e97adb321f957c2cd6ac992f3223ffe2be529ea1649f
@ -530,7 +530,7 @@ history_value_55:
history_value_56:
id: 56
setting_id: 56
value: '7064'
value: Event reservation
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.977369'
footprint: 5ce909aa92740dbd65647ff3a5d5fdc75ad6c2a02b5fa9bbeea1aa27f907416a
@ -539,7 +539,7 @@ history_value_56:
history_value_57:
id: 57
setting_id: 57
value: Event reservation
value: '7064'
created_at: '2019-09-20 11:02:32.125400'
updated_at: '2021-05-31 15:00:36.993279'
footprint: ed7ecf389b9560728ce43257bef442a2404f463ebbc0fbaeaad05f113cca21df
@ -824,3 +824,39 @@ history_value_86:
updated_at: '2021-06-15 12:05:21.513651'
footprint: 350ca0033f3e65b6e8186dbb0545b55ecb3768e1a08501549b81d95e34809440
invoicing_profile_id: 1
history_value_87:
id: 87
setting_id: 86
value: Prepaid pack
created_at: '2022-10-26 12:46:16.125400000 Z'
updated_at: '2022-10-26 12:46:16.125400000 Z'
footprint:
invoicing_profile_id: 1
history_value_88:
id: 88
setting_id: 87
value: '7066'
created_at: '2022-10-26 12:46:16.125400000 Z'
updated_at: '2022-10-26 12:46:16.125400000 Z'
footprint:
invoicing_profile_id: 1
history_value_89:
id: 89
setting_id: 88
value: Shop order
created_at: '2022-10-26 12:46:16.125400000 Z'
updated_at: '2022-10-26 12:46:16.125400000 Z'
footprint:
invoicing_profile_id: 1
history_value_90:
id: 90
setting_id: 89
value: '7067'
created_at: '2022-10-26 12:46:16.125400000 Z'
updated_at: '2022-10-26 12:46:16.125400000 Z'
footprint:
invoicing_profile_id: 1

View File

@ -244,3 +244,32 @@ pgo35:
item_id: 10
gateway_object_type: 'Stripe::Customer'
gateway_object_id: 'cus_IhIynmoJbzLpwX'
pgo36:
id: 36
gateway_object_id: pi_3LpALs2sOmf47Nz91QyFI7nO
gateway_object_type: Stripe::PaymentIntent
item_type: Invoice
item_id: 5816
payment_gateway_object_id:
pgo37:
id: 37
gateway_object_id: pi_3LpBa12sOmf47Nz91QFYVXVf
gateway_object_type: Stripe::PaymentIntent
item_type: Invoice
item_id: 5817
payment_gateway_object_id:
pgo38:
id: 38
gateway_object_id: pi_3LpBjD2sOmf47Nz90bOsclbx
gateway_object_type: Stripe::PaymentIntent
item_type: Invoice
item_id: 5818
payment_gateway_object_id:
pgo39:
id: 39
gateway_object_id: pi_3LpBwR2sOmf47Nz90EILNKvi
gateway_object_type: Stripe::PaymentIntent
item_type: Invoice
item_id: 5819
payment_gateway_object_id:

View File

@ -502,3 +502,27 @@ setting_85:
name: public_agenda_module
created_at: 2020-04-15 14:38:40.000421500 Z
updated_at: 2020-04-15 14:38:40.000421500 Z
setting_86:
id: 86
name: accounting_Pack_label
created_at: 2022-10-26 12:46:16.125400000 Z
updated_at: 2022-10-26 12:46:16.125400000 Z
setting_87:
id: 87
name: accounting_Pack_code
created_at: 2022-10-26 12:46:16.125400000 Z
updated_at: 2022-10-26 12:46:16.125400000 Z
setting_88:
id: 88
name: accounting_Product_label
created_at: 2022-10-26 12:46:16.125400000 Z
updated_at: 2022-10-26 12:46:16.125400000 Z
setting_89:
id: 89
name: accounting_Product_code
created_at: 2022-10-26 12:46:16.125400000 Z
updated_at: 2022-10-26 12:46:16.125400000 Z

View File

@ -36,7 +36,7 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest
# Check the export was created correctly
res = json_response(response.body)
e = Export.where(id: res[:export_id]).first
e = Export.find(res[:export_id])
assert_not_nil e, 'Export was not created in database'
# Run the worker
@ -52,94 +52,135 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest
data = CSV.read(e.file, headers: true, col_sep: e.key)
# test values
# first line = client line
journal_code = Setting.get('accounting_journal_code')
assert_equal journal_code, data[0][I18n.t('accounting_export.journal_code')], 'Wrong journal code'
first_invoice = Invoice.first
entry_date = first_invoice.created_at.to_date
assert_equal entry_date, DateTime.parse(data[0][I18n.t('accounting_export.date')]), 'Wrong date'
if first_invoice.paid_by_card?
card_client_code = Setting.get('accounting_card_client_code')
assert_equal card_client_code, data[0][I18n.t('accounting_export.account_code')], 'Account code for card client is wrong'
card_client_label = Setting.get('accounting_card_client_label')
assert_equal card_client_label, data[0][I18n.t('accounting_export.account_label')], 'Account label for card client is wrong'
else
warn "WARNING: unable to test accurately accounting export: invoice #{first_invoice.id} was not paid by card"
end
assert_equal first_invoice.reference, data[0][I18n.t('accounting_export.piece')], 'Piece (invoice reference) is wrong'
if first_invoice.subscription_invoice?
assert_match I18n.t('accounting_export.subscription'),
data[0][I18n.t('accounting_export.line_label')],
'Line label does not contains the reference to the invoiced item'
else
warn "WARNING: unable to test accurately accounting export: invoice #{first_invoice.id} does not have a subscription"
end
if first_invoice.wallet_transaction_id.nil?
assert_equal first_invoice.total / 100.00, data[0][I18n.t('accounting_export.debit_origin')].to_f,
'Origin debit amount does not match'
assert_equal first_invoice.total / 100.00, data[0][I18n.t('accounting_export.debit_euro')].to_f, 'Euro debit amount does not match'
else
warn "WARNING: unable to test accurately accounting export: invoice #{first_invoice.id} is using wallet"
end
assert_equal 0, data[0][I18n.t('accounting_export.credit_origin')].to_f, 'Credit origin amount does not match'
assert_equal 0, data[0][I18n.t('accounting_export.credit_euro')].to_f, 'Credit euro amount does not match'
# first line = client line
check_client_line(first_invoice, data[0])
# second line = sold item line
assert_equal journal_code, data[1][I18n.t('accounting_export.journal_code')], 'Wrong journal code'
assert_equal entry_date, DateTime.parse(data[1][I18n.t('accounting_export.date')]), 'Wrong date'
check_item_line(first_invoice, first_invoice.invoice_items.first, data[1])
if first_invoice.subscription_invoice?
subscription_code = Setting.get('accounting_subscription_code')
assert_equal subscription_code, data[1][I18n.t('accounting_export.account_code')], 'Account code for subscription is wrong'
# ensure invoice 4 is not exported (0€ invoice)
zero_invoice = Invoice.find(4)
assert_nil(data.map { |line| line[I18n.t('accounting_export.piece')] }.find { |document| document == zero_invoice.reference },
'Invoice at 0 should not be exported')
subscription_label = Setting.get('accounting_subscription_label')
assert_equal subscription_label, data[1][I18n.t('accounting_export.account_label')], 'Account label for subscription is wrong'
end
assert_equal first_invoice.reference, data[1][I18n.t('accounting_export.piece')], 'Piece (invoice reference) is wrong'
assert_match I18n.t('accounting_export.subscription'),
data[1][I18n.t('accounting_export.line_label')],
'Line label should be empty for non client lines'
item = first_invoice.invoice_items.first
assert_equal item.amount / 100.00, data[1][I18n.t('accounting_export.credit_origin')].to_f, 'Origin credit amount does not match'
assert_equal item.amount / 100.00, data[1][I18n.t('accounting_export.credit_euro')].to_f, 'Euro credit amount does not match'
assert_equal 0, data[1][I18n.t('accounting_export.debit_origin')].to_f, 'Debit origin amount does not match'
assert_equal 0, data[1][I18n.t('accounting_export.debit_euro')].to_f, 'Debit euro amount does not match'
# test with another invoice
# test with a reservation invoice
machine_invoice = Invoice.find(5)
client_row = data[data.length - 4]
item_row = data[data.length - 3]
check_client_line(machine_invoice, data[6])
check_item_line(machine_invoice, machine_invoice.invoice_items.first, data[7])
data.each do |a|
p a
end
if machine_invoice.main_item.object_type == 'Reservation' && machine_invoice.main_item.object.reservable_type == 'Machine'
assert_match I18n.t('accounting_export.Machine_reservation'),
client_row[I18n.t('accounting_export.line_label')],
'Line label does not contains the reference to the invoiced item'
machine_code = Setting.get('accounting_Machine_code')
assert_equal machine_code, item_row[I18n.t('accounting_export.account_code')], 'Account code for machine reservation is wrong'
machine_label = Setting.get('accounting_Machine_label')
assert_equal machine_label, item_row[I18n.t('accounting_export.account_label')], 'Account label for machine reservation is wrong'
else
warn "WARNING: unable to test accurately accounting export: invoice #{machine_invoice.id} is not a Machine reservation"
end
# test with a shop order invoice (local payment)
shop_invoice = Invoice.find(5811)
check_client_line(shop_invoice, data[10])
check_item_line(shop_invoice, shop_invoice.invoice_items.first, data[11])
check_item_line(shop_invoice, shop_invoice.invoice_items.last, data[12])
# Clean CSV file
require 'fileutils'
FileUtils.rm(e.file)
end
def check_client_line(invoice, client_line)
check_journal_code(client_line)
check_entry_date(invoice, client_line)
check_client_accounts(invoice, client_line)
check_entry_label(invoice, client_line)
check_document(invoice, client_line)
if invoice.wallet_transaction_id.nil?
assert_equal invoice.total / 100.00, client_line[I18n.t('accounting_export.debit_origin')].to_f,
'Origin debit amount does not match'
assert_equal invoice.total / 100.00, client_line[I18n.t('accounting_export.debit_euro')].to_f, 'Euro debit amount does not match'
else
warn "WARNING: unable to test accurately accounting export: invoice #{invoice.id} is using wallet"
end
assert_equal 0, client_line[I18n.t('accounting_export.credit_origin')].to_f, 'Credit origin amount does not match'
assert_equal 0, client_line[I18n.t('accounting_export.credit_euro')].to_f, 'Credit euro amount does not match'
end
def check_item_line(invoice, invoice_item, item_line)
check_journal_code(item_line)
check_entry_date(invoice, item_line)
check_subscription_accounts(invoice, item_line)
check_reservation_accounts(invoice, item_line)
check_document(invoice, item_line)
check_entry_label(invoice, item_line)
assert_equal invoice_item.amount / 100.00, item_line[I18n.t('accounting_export.credit_origin')].to_f,
'Origin credit amount does not match'
assert_equal invoice_item.amount / 100.00, item_line[I18n.t('accounting_export.credit_euro')].to_f, 'Euro credit amount does not match'
assert_equal 0, item_line[I18n.t('accounting_export.debit_origin')].to_f, 'Debit origin amount does not match'
assert_equal 0, item_line[I18n.t('accounting_export.debit_euro')].to_f, 'Debit euro amount does not match'
end
def check_journal_code(line)
journal_code = Setting.get('accounting_journal_code')
assert_equal journal_code, line[I18n.t('accounting_export.journal_code')], 'Wrong journal code'
end
def check_entry_date(invoice, line)
entry_date = invoice.created_at.to_date
assert_equal entry_date, DateTime.parse(line[I18n.t('accounting_export.date')]), 'Wrong date'
end
def check_client_accounts(invoice, client_line)
if invoice.wallet_transaction && invoice.wallet_amount.positive?
wallet_client_code = Setting.get('accounting_wallet_client_code')
assert_equal wallet_client_code, client_line[I18n.t('accounting_export.account_code')], 'Account code for wallet client is wrong'
wallet_client_label = Setting.get('accounting_wallet_client_label')
assert_equal wallet_client_label, client_line[I18n.t('accounting_export.account_label')], 'Account label for wallet client is wrong'
end
mean = invoice.paid_by_card? ? 'card' : 'other'
client_code = Setting.get("accounting_#{mean}_client_code")
assert_equal client_code, client_line[I18n.t('accounting_export.account_code')], 'Account code for client is wrong'
client_label = Setting.get("accounting_#{mean}_client_label")
assert_equal client_label, client_line[I18n.t('accounting_export.account_label')], 'Account label for client is wrong'
end
def check_subscription_accounts(invoice, item_line)
return unless invoice.subscription_invoice?
subscription_code = Setting.get('accounting_subscription_code')
assert_equal subscription_code, item_line[I18n.t('accounting_export.account_code')], 'Account code for subscription is wrong'
subscription_label = Setting.get('accounting_subscription_label')
assert_equal subscription_label, item_line[I18n.t('accounting_export.account_label')], 'Account label for subscription is wrong'
end
def check_reservation_accounts(invoice, item_line)
return unless invoice.main_item.object_type == 'Reservation'
code = Setting.get("accounting_#{invoice.main_item.object.reservable_type}_code")
assert_equal code, item_line[I18n.t('accounting_export.account_code')], 'Account code for reservation is wrong'
label = Setting.get("accounting_#{invoice.main_item.object.reservable_type}_label")
assert_equal label, item_line[I18n.t('accounting_export.account_label')], 'Account label for reservation is wrong'
end
def check_document(invoice, line)
assert_equal(invoice.reference, line[I18n.t('accounting_export.piece')], 'Document (invoice reference) is wrong')
end
def check_entry_label(invoice, line)
if invoice.subscription_invoice?
assert_match I18n.t('accounting_export.subscription'),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the subscription'
end
if invoice.main_item.object_type == 'Reservation'
assert_match I18n.t("accounting_export.#{invoice.main_item.object.reservable_type}_reservation"),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the reservation'
end
return unless invoice.main_item.object_type == 'WalletTransaction'
assert_match I18n.t('accounting_export.wallet'),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the wallet'
end
end