From c5211e98e3f44df74ea30132bebd6f3c49bc4ef0 Mon Sep 17 00:00:00 2001 From: Du Peng Date: Thu, 23 Dec 2021 19:36:23 +0100 Subject: [PATCH] generate invoice with multi vat --- app/models/invoice_item.rb | 17 +++++++- app/pdfs/pdf/invoice.rb | 38 ++++++++++------- app/services/vat_history_service.rb | 64 ++++++++++++++--------------- config/locales/de.yml | 2 +- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/no.yml | 2 +- config/locales/pt.yml | 2 +- 9 files changed, 77 insertions(+), 54 deletions(-) diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index 3b9865100..4c5536fa4 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -27,7 +27,7 @@ class InvoiceItem < Footprintable def net_amount # deduct VAT vat_service = VatHistoryService.new - vat_rate = vat_service.invoice_vat(invoice) + vat_rate = vat_service.invoice_vat(self) Rational(amount_after_coupon / (vat_rate / 100.00 + 1)).round.to_f end @@ -36,6 +36,21 @@ class InvoiceItem < Footprintable amount_after_coupon - net_amount end + # return invoice item type (Matchine/Training/Space/Event/Subscription) + def invoice_item_type + if object_type == Reservation.name + reservable_type = object.try(:reservable_type) + if reservable_type + return reservable_type + else + return '' + end + elsif object_type == Subscription.name + return Subscription.name + end + '' + end + private def log_changes diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb index 4065fe6b1..a4612ab3e 100644 --- a/app/pdfs/pdf/invoice.rb +++ b/app/pdfs/pdf/invoice.rb @@ -230,10 +230,18 @@ class PDF::Invoice < Prawn::Document # TVA vat_service = VatHistoryService.new - vat_rate = vat_service.invoice_vat(invoice) - if vat_rate != 0 + vat_rate_group = {} + invoice.invoice_items.each do |item| + vat_type = item.invoice_item_type + vat_rate_group[vat_type] = { vat_rate: vat_service.invoice_vat(item), total_vat: 0, amount: 0 } unless vat_rate_group[vat_type] + vat_rate_group[vat_type][:total_vat] += item.vat + vat_rate_group[vat_type][:amount] += item.amount.to_i + end + if total_vat != 0 data += [[I18n.t('invoices.total_including_all_taxes'), number_to_currency(total)]] - data += [[I18n.t('invoices.including_VAT_RATE', RATE: vat_rate), number_to_currency(total_vat / 100.00)]] + vat_rate_group.each do |_type, rate| + data += [[I18n.t('invoices.including_VAT_RATE', RATE: rate[:vat_rate], AMOUNT: number_to_currency(rate[:amount] / 100.00)), number_to_currency(rate[:total_vat] / 100.00)]] + end data += [[I18n.t('invoices.including_total_excluding_taxes'), number_to_currency(total_ht / 100.00)]] data += [[I18n.t('invoices.including_amount_payed_on_ordering'), number_to_currency(total)]] @@ -252,23 +260,25 @@ class PDF::Invoice < Prawn::Document row(0).font_style = :bold column(1).style align: :right - if Setting.get('invoice_VAT-active') + if total_vat != 0 # Total incl. taxes row(-1).style align: :right row(-1).background_color = 'E4E4E4' row(-1).font_style = :bold - # including VAT xx% - row(-2).style align: :right - row(-2).background_color = 'E4E4E4' - row(-2).font_style = :italic + vat_rate_group.size.times do |i| + # including VAT xx% + row(-2 - i).style align: :right + row(-2 - i).background_color = 'E4E4E4' + row(-2 - i).font_style = :italic + end # including total excl. taxes - row(-3).style align: :right - row(-3).background_color = 'E4E4E4' - row(-3).font_style = :italic + row(-3 - vat_rate_group.size + 1).style align: :right + row(-3 - vat_rate_group.size + 1).background_color = 'E4E4E4' + row(-3 - vat_rate_group.size + 1).font_style = :italic # including amount payed on ordering - row(-4).style align: :right - row(-4).background_color = 'E4E4E4' - row(-4).font_style = :bold + row(-4 - vat_rate_group.size + 1).style align: :right + row(-4 - vat_rate_group.size + 1).background_color = 'E4E4E4' + row(-4 - vat_rate_group.size + 1).font_style = :bold end end diff --git a/app/services/vat_history_service.rb b/app/services/vat_history_service.rb index d0b354099..c072d0ac2 100644 --- a/app/services/vat_history_service.rb +++ b/app/services/vat_history_service.rb @@ -2,35 +2,29 @@ # Provides the VAT rate in use at the given date class VatHistoryService - # return the VAT rate for the given Invoice/Avoir + # return the VAT rate for the given InvoiceItem def invoice_vat(invoice_item) if invoice_item.invoice.is_a?(Avoir) - vat_rate(invoice.avoir_date, vat_type(invoice_item)) + vat_rate(invoice_item.invoice.avoir_date, invoice_item.invoice_item_type) else - vat_rate(invoice.created_at, vat_type(invoice_item)) + vat_rate(invoice_item.invoice.created_at, invoice_item.invoice_item_type) end end - # return the VAT rate for the given date + # return the VAT rate for the given date and vat type def vat_rate(date, vat_rate_type) - @vat_rates = vat_history(vat_rate_type) if @vat_rates.nil? + vat_rates = vat_history(vat_rate_type) - first_rate = @vat_rates.first + first_rate = vat_rates.first return first_rate[:rate] if date < first_rate[:date] - @vat_rates.each_index do |i| - return @vat_rates[i][:rate] if date >= @vat_rates[i][:date] && (@vat_rates[i + 1].nil? || date < @vat_rates[i + 1][:date]) + vat_rates.each_index do |i| + return vat_rates[i][:rate] if date >= vat_rates[i][:date] && (vat_rates[i + 1].nil? || date < vat_rates[i + 1][:date]) end end private - def vat_type(invoice_item) - return invoice_item.object.reservable_type if invoice_item.object_type == 'Reservation' - - 'Subscription' - end - def vat_history(vat_rate_type) chronology = [] end_date = DateTime.current @@ -41,28 +35,32 @@ class VatHistoryService chronology.push(start: DateTime.new(0), end: end_date, enabled: false) date_rates = [] vat_rate_history_values = [] - vat_rate_by_type = Setting.find_by(name: "invoice_VAT-rate_#{vat_rate_type}")&.history_values&.order(created_at: 'ASC') - first_vat_rate_by_type = vat_rate_by_type.select { |v| v.value.present? }.first - if first_vat_rate_by_type - vat_rate_history_values = Setting.find_by(name: 'invoice_VAT-rate').history_values.where('created_at < ?', first_vat_rate_by_type.created_at).order(created_at: 'ASC').to_a - vat_rate_by_type = Setting.find_by(name: "invoice_VAT-rate_#{vat_rate_type}").history_values.where('created_at >= ?', first_vat_rate_by_type.created_at).order(created_at: 'ASC') - vat_rate_by_type.each do |rate| - if rate.value.blank? - vat_rate = Setting.find_by(name: 'invoice_VAT-rate').history_values.where('created_at < ?', rate.created_at).order(created_at: 'DESC').first - rate.value = vat_rate.value + if vat_rate_type.present? + vat_rate_by_type = Setting.find_by(name: "invoice_VAT-rate_#{vat_rate_type}")&.history_values&.order(created_at: 'ASC') + first_vat_rate_by_type = vat_rate_by_type.select { |v| v.value.present? }.first + if first_vat_rate_by_type + vat_rate_history_values = Setting.find_by(name: 'invoice_VAT-rate').history_values.where('created_at < ?', first_vat_rate_by_type.created_at).order(created_at: 'ASC').to_a + vat_rate_by_type = Setting.find_by(name: "invoice_VAT-rate_#{vat_rate_type}").history_values.where('created_at >= ?', first_vat_rate_by_type.created_at).order(created_at: 'ASC') + vat_rate_by_type.each do |rate| + if rate.value.blank? + vat_rate = Setting.find_by(name: 'invoice_VAT-rate').history_values.where('created_at < ?', rate.created_at).order(created_at: 'DESC').first + rate.value = vat_rate.value + end + vat_rate_history_values.push(rate) end - vat_rate_history_values.push(rate) + else + vat_rate_history_values = Setting.find_by(name: 'invoice_VAT-rate').history_values.order(created_at: 'ASC').to_a + end + vat_rate_history_values.each do |rate| + range = chronology.select { |p| rate.created_at.to_i.between?(p[:start].to_i, p[:end].to_i) }.first + date = range[:enabled] ? rate.created_at : range[:end] + date_rates.push(date: date, rate: rate.value.to_i) + end + chronology.reverse_each do |period| + date_rates.push(date: period[:start], rate: 0) unless period[:enabled] end else - vat_rate_history_values = Setting.find_by(name: 'invoice_VAT-rate').history_values.order(created_at: 'ASC').to_a - end - vat_rate_history_values.each do |rate| - range = chronology.select { |p| rate.created_at.to_i.between?(p[:start].to_i, p[:end].to_i) }.first - date = range[:enabled] ? rate.created_at : range[:end] - date_rates.push(date: date, rate: rate.value.to_i) - end - chronology.reverse_each do |period| - date_rates.push(date: period[:start], rate: 0) unless period[:enabled] + date_rates.push(date: chronology[-1][:start], rate: 0) end date_rates.sort_by { |k| k[:date] } end diff --git a/config/locales/de.yml b/config/locales/de.yml index 0d0db4abc..dae2ebc68 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -90,7 +90,7 @@ de: other: "%{count} %{NAME}-Tickets" coupon_CODE_discount_of_DISCOUNT: "Gutschein {CODE}: Rabatt von {DISCOUNT}{TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Gesamtpreis inkl. Steuern" - including_VAT_RATE: "Inklusive MwSt. %{RATE}%" + including_VAT_RATE: "Inklusive MwSt. %{RATE} von %{AMOUNT}" including_total_excluding_taxes: "Gesamtbetrag zzgl. Steuern" including_amount_payed_on_ordering: "Inklusive bei Bestellung bezahlter Betrag" total_amount: "Gesamtbetrag" diff --git a/config/locales/en.yml b/config/locales/en.yml index a6e36f3f4..a25312b9d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -90,7 +90,7 @@ en: other: "%{count} %{NAME} tickets" coupon_CODE_discount_of_DISCOUNT: "Coupon {CODE}: discount of {DISCOUNT}{TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Total incl. all taxes" - including_VAT_RATE: "Including VAT %{RATE}%" + including_VAT_RATE: "Including VAT %{RATE}% of %{AMOUNT}" including_total_excluding_taxes: "Including Total excl. taxes" including_amount_payed_on_ordering: "Including amount payed on ordering" total_amount: "Total amount" diff --git a/config/locales/es.yml b/config/locales/es.yml index 6ac0679a4..231aa31d2 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -90,7 +90,7 @@ es: other: "%{count} %{NAME} entradas" coupon_CODE_discount_of_DISCOUNT: "Cupón {CODE}: descuento de {DISCOUNT}{TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Total impuestos incluidos" - including_VAT_RATE: "Incluyendo IVA %{RATE}%" + including_VAT_RATE: "Incluyendo IVA %{RATE}% de %{AMOUNT}" including_total_excluding_taxes: "Excluyendo IVA" including_amount_payed_on_ordering: "Incluyendo cantidad pagada en el pedido" total_amount: "Precio total" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2dcf06c18..4da5c99c6 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -90,7 +90,7 @@ fr: other: "%{count} places %{NAME}" coupon_CODE_discount_of_DISCOUNT: "Code {CODE} : remise de {DISCOUNT} {TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Total TTC" - including_VAT_RATE: "Dont TVA %{RATE}%" + including_VAT_RATE: "Dont TVA %{RATE}% de %{AMOUNT}" including_total_excluding_taxes: "Dont total HT" including_amount_payed_on_ordering: "Dont montant payé à la commande" total_amount: "Montant total" diff --git a/config/locales/no.yml b/config/locales/no.yml index 36a5b70d1..3ff47f017 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -90,7 +90,7 @@ other: "%{count} %{NAME} tickets" coupon_CODE_discount_of_DISCOUNT: "Kupong {CODE}: rabatt på {DISCOUNT}{TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Sum inkl. MVA" - including_VAT_RATE: "Inkludert MVA %{RATE}%" + including_VAT_RATE: "Inkludert MVA %{RATE}% %{AMOUNT}" including_total_excluding_taxes: "Inkludert total ekskl. MVA" including_amount_payed_on_ordering: "Inkludert beløp betalt ved bestilling" total_amount: "Totalbeløp" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 851f11eb6..a63dc2415 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -90,7 +90,7 @@ pt: other: "%{count} %{NAME} tickets" coupon_CODE_discount_of_DISCOUNT: "Cupom {CODE}: desconto de {DISCOUNT}{TYPE, select, percent_off{%} other{}}" #messageFormat interpolation total_including_all_taxes: "Total de taxas inclusas" - including_VAT_RATE: "Incluindo VAT %{RATE}%" + including_VAT_RATE: "Incluindo VAT %{RATE}% de %{AMOUNT}" including_total_excluding_taxes: "Incluindo total de faixas exclusas" including_amount_payed_on_ordering: "Incluindo o valor pago na encomenda" total_amount: "Montante total"