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:
parent
5acd44e51d
commit
b3072ec444
@ -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)
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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'},
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user