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

(feat) auto fix rouding errors on accounting lines

Also: (ui) added help tooltips on some input fields
This commit is contained in:
Sylvain 2022-11-21 17:34:06 +01:00
parent 5acd44e51d
commit b3072ec444
11 changed files with 62 additions and 55 deletions

View File

@ -1,5 +1,7 @@
# Changelog Fab-manager
- [TODO DEPLOY] `rails fablab:maintenance:build_accounting_lines`
- Add reservation deadline parameter (#414)
- Fix a bug: unable to run test in negavtive timezones (#425)

View File

@ -115,7 +115,9 @@ export const AccountingCodesSettings: React.FC<AccountingCodesSettingsProps> = (
<h4>{t('app.admin.accounting_codes_settings.error')}</h4>
<div className="error">
<FormInput register={register} id="accounting_Error_code" label={t('app.admin.accounting_codes_settings.code')} />
<FormInput register={register} id="accounting_Error_label" label={t('app.admin.accounting_codes_settings.label')} />
<FormInput register={register} id="accounting_Error_label"
label={t('app.admin.accounting_codes_settings.label')}
tooltip={t('app.admin.accounting_codes_settings.error_help')} />
</div>
<h4>{t('app.admin.accounting_codes_settings.advanced_accounting')}</h4>
<FormSwitch control={control} id="advanced_accounting"

View File

@ -30,6 +30,7 @@ export const PasswordInput = <TFieldValues extends FieldValues>({ register, curr
}}
formState={formState}
label={t('app.shared.password_input.new_password')}
tooltip={t('app.shared.password_input.help')}
type="password" />
<FormInput id="password_confirmation"
register={register}

View File

@ -31,6 +31,8 @@ class Accounting::AccountingService
vat = vat_line(invoice)
lines << vat unless vat.nil?
fix_rounding_errors(lines)
lines
end
@ -137,4 +139,17 @@ class Accounting::AccountingService
"#{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] == 'client' }.pluck(:debit).sum
credit_sum = lines.filter { |l| l[:line_type] != 'client' }.pluck(:credit).sum
return if debit_sum == credit_sum
diff = debit_sum - credit_sum
fixable_line = lines.filter { |l| l[:line_type] == 'item' }.last
fixable_line.credit += diff
end
end

View File

@ -159,6 +159,7 @@ en:
prepaid_pack: "Pack of prepaid-hours"
product: "Product of the store"
error: "Erroneous invoices"
error_help: "As part of a maintenance operation, it may exceptionally happen that invoices, that have been generated by mistake due to a bug in the software, are discovered. As these invoices cannot be deleted, they will be exported to the account defined here. Please manually cancel these invoices."
advanced_accounting: "Advanced accounting"
enable_advanced: "Enable the advanced accounting"
enable_advanced_help: "This will enable the ability to have custom accounting codes per resources (machines, spaces, training ...). These codes can be modified on each resource edition form."

View File

@ -110,6 +110,7 @@ en:
password_input:
new_password: "New password"
confirm_password: "Confirm password"
help: "Your password must be minimum 12 characters long, have at least one uppercase letter, one lowercase letter, one number and one special character."
password_too_short: "Password is too short (must be at least 12 characters)"
confirmation_mismatch: "Confirmation mismatch with password."
#project edition form

View File

@ -8,9 +8,9 @@ require 'integrity/archive_helper'
# Invoice.invoiced and the subscription was saved in InvoiceItem.subscription_id
#
# From this migration, everything will be saved in InvoiceItems.object_id & InvoiceItem.object_type. This will be more
# extensible and will allow to invoice more types of objects the future.
# extensible and will allow to invoice more types of objects in the future.
class AddObjectToInvoiceItem < ActiveRecord::Migration[5.2]
def up
def up # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# first check that there's no bogus invoices
raise InvalidInvoiceError if Invoice.where(invoiced_id: nil).where.not(invoiced_type: 'Error').count.positive?
@ -40,7 +40,7 @@ class AddObjectToInvoiceItem < ActiveRecord::Migration[5.2]
main = true
WHERE id = #{invoice.invoice_items.where(subscription_id: nil).first.id}
)
invoice.invoice_items.where(subscription_id: nil)[1..-1].each do |ii|
invoice.invoice_items.where(subscription_id: nil)[1..].each do |ii|
execute %(
UPDATE invoice_items
SET object_id = #{invoice.invoiced_id || 'NULL'},

View File

@ -995,6 +995,8 @@ Setting.set('store_hidden', true) unless Setting.find_by(name: 'store_hidden').t
Setting.set('advanced_accounting', false) unless Setting.find_by(name: 'advanced_accounting').try(:value)
Setting.set('accounting_VAT_code', '4457') unless Setting.find_by(name: 'accounting_VAT_code').try(:value)
if StatisticCustomAggregation.count.zero?
# available reservations hours for machines
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2)

View File

@ -5,11 +5,11 @@ namespace :fablab do
namespace :fix do
desc '[release 2.3.0] update reservations referencing reservables not present in database'
task reservations_not_existing_reservable: :environment do
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.logger = Logger.new($stdout)
ActiveRecord::Base.connection.execute(
'UPDATE reservations SET reservable_type = NULL, reservable_id = NULL'\
' WHERE NOT EXISTS (SELECT 1 FROM events WHERE events.id = reservations.reservable_id)'\
' AND reservations.reservable_type = \'Event\''
'UPDATE reservations SET reservable_type = NULL, reservable_id = NULL ' \
'WHERE NOT EXISTS (SELECT 1 FROM events WHERE events.id = reservations.reservable_id) ' \
"AND reservations.reservable_type = 'Event'"
)
end
@ -98,7 +98,7 @@ namespace :fablab do
task categories_slugs: :environment do
Category.all.each do |cat|
# rubocop:disable Layout/LineLength
`curl -XPOST http://#{ENV['ELASTICSEARCH_HOST']}:9200/stats/event/_update_by_query?conflicts=proceed\\&refresh\\&wait_for_completion -d '
`curl -XPOST http://#{ENV.fetch('ELASTICSEARCH_HOST', nil)}:9200/stats/event/_update_by_query?conflicts=proceed\\&refresh\\&wait_for_completion -d '
{
"script": {
"source": "ctx._source.subType = params.slug",
@ -279,39 +279,5 @@ namespace :fablab do
puts i
end
end
desc '[release 5.6.0] fix rounding issue on invoices using coupon'
task invoices_rounding: :environment do
coupon_service = CouponService.new
vat_service = VatHistoryService.new
# check invoices and udpate erroneous
Invoice.find_each do |invoice|
main_item = invoice.main_item.amount
other_items = invoice.other_items.map(&:amount).sum
total_without_coupon = coupon_service.invoice_total_no_coupon(invoice)
main_item_net = coupon_service.ventilate(total_without_coupon, main_item, invoice.coupon)
other_items_net = coupon_service.ventilate(total_without_coupon, other_items, invoice.coupon)
vat_rate_groups = vat_service.invoice_vat(invoice)
total_vat = vat_rate_groups.values.pluck(:total_vat).sum
total_excl_taxes = invoice.invoice_items.map(&:net_amount).sum
new_total = total_vat + total_excl_taxes
if (invoice.total != main_item_net + other_items_net) || (new_total != invoice.total)
Rails.logger.info "Fixing invoice #{invoice.id}... Previous total = #{invoice.total}, fixed total = #{new_total}"
invoice.update(total: new_total)
end
end
# chain invoices
if AccountingPeriod.count.positive?
last_period = AccountingPeriod.order(start_at: :desc).first
puts "Regenerating from #{last_period.end_at}..."
Invoice.where('created_at > ?', last_period.end_at).order(:id).each(&:chain_record)
else
puts '(Re)generating all footprint...'
Invoice.order(:id).all.each(&:chain_record)
end
end
end
end

View File

@ -5,10 +5,7 @@ namespace :fablab do
namespace :maintenance do
desc 'Regenerate the invoices (invoices & avoirs) PDF'
task :regenerate_invoices, %i[year month] => :environment do |_task, args|
year = args.year || Time.current.year
month = args.month || Time.current.month
start_date = Time.zone.local(year.to_i, month.to_i, 1)
end_date = start_date.next_month
start_date, end_date = dates_from_args(args)
puts "-> Start regenerate the invoices PDF between #{I18n.l start_date, format: :long} and " \
"#{I18n.l end_date - 1.minute, format: :long}"
invoices = Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start_date, end_date: end_date)
@ -18,10 +15,7 @@ namespace :fablab do
end
task :regenerate_schedules, %i[year month] => :environment do |_task, args|
year = args.year || Time.current.year
month = args.month || Time.current.month
start_date = Time.zone.local(year.to_i, month.to_i, 1)
end_date = start_date.next_month
start_date, end_date = dates_from_args(args)
puts "-> Start regenerate the payment schedules PDF between #{I18n.l start_date, format: :long} and " \
"#{I18n.l end_date - 1.minute, format: :long}"
schedules = PaymentSchedule.where('created_at >= :start_date AND created_at < :end_date', start_date: start_date, end_date: end_date)
@ -126,10 +120,7 @@ namespace :fablab do
desc 'Regenerate the invoices (invoices & avoirs) reference'
task :regenerate_invoices_reference, %i[year month] => :environment do |_task, args|
year = args.year || Time.current.year
month = args.month || Time.current.month
start_date = Time.zone.local(year.to_i, month.to_i, 1)
end_date = start_date.next_month
start_date, end_date = dates_from_args(args)
puts "-> Start regenerate the invoices reference between #{I18n.l start_date, format: :long} and " \
"#{I18n.l end_date - 1.minute, format: :long}"
invoices = Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start_date, end_date: end_date)
@ -137,5 +128,22 @@ namespace :fablab do
invoices.each(&:update_reference)
puts '-> Done'
end
desc 'Regenerate accounting lines'
task :regenerate_accounting_lines, %i[year month] => :environment do |_task, args|
start_date, end_date = dates_from_args(args)
puts "-> Start regenerate the accounting lines between #{I18n.l start_date, format: :long} and " \
"#{I18n.l end_date - 1.minute, format: :long}"
AccountingLine.where(date: start_date..end_date).destroy_all
Accounting::AccountingService.new.build(start_date.beginning_of_day, end_date.end_of_day)
puts '-> Done'
end
def dates_from_args(args)
year = args.year || Time.current.year
month = args.month || Time.current.month
start_date = Time.zone.local(year.to_i, month.to_i, 1)
[start_date, start_date.next_month]
end
end
end

View File

@ -127,6 +127,15 @@ namespace :fablab do
print "\e[32m✅\e[0m \e[1mDone\e[0m\n"
end
desc 'generate acconting lines'
task build_accounting_lines: :environment do
start_date = Invoice.order(created_at: :asc).first&.created_at
end_date = DateTime.current
AccountingLine.where(date: start_date..end_date).destroy_all
Accounting::AccountingService.new.build(start_date&.beginning_of_day, end_date.end_of_day)
puts '-> Done'
end
def select_group(groups)
groups.each do |g|
print "#{g.id}) #{g.name}\n"