add vat exempt option in payment_tab ~ refs #automatic_go_invoices

add option vor vat_exempt inoices ~ refs automatic go invoices

fix order.ordergroup nil pointer error ~ automatic go invoices

fix nil pointer~ refs automatic go invoice

rubocop styling ~ refs automatic go invoice
This commit is contained in:
viehlieb 2022-01-27 12:42:29 +01:00
parent bafa163ce5
commit 81a5194023
8 changed files with 122 additions and 35 deletions

View file

@ -84,7 +84,7 @@ class Finance::BalancingController < Finance::BaseController
note = t('finance.balancing.close.notice') note = t('finance.balancing.close.notice')
if @order.closed? if @order.closed?
alert = t('finance.balancing.close.alert') alert = t('finance.balancing.close.alert')
if FoodsoftConfig[:group_order_invoices]&.[](:use) if FoodsoftConfig[:group_order_invoices]&.[](:use_automatic_invoices)
@order.group_orders.each do |go| @order.group_orders.each do |go|
alert = t('finance.balancing.close.settings_not_set') alert = t('finance.balancing.close.settings_not_set')
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)

View file

@ -64,6 +64,64 @@ class GroupOrderInvoicePdf < RenderPDF
move_down 5 move_down 5
#------------- Table Data ----------------------- #------------- Table Data -----------------------
@group_order = GroupOrder.find(@options[:group_order].id)
if FoodsoftConfig[:group_order_invoices][:vat_exempt]
body_for_vat_exempt
else
body_with_vat
end
end
def body_for_vat_exempt
total_gross = 0
data = [I18n.t('documents.group_order_invoice_pdf.vat_exempt_rows')]
move_down 10
group_order_articles = GroupOrderArticle.where(group_order_id: @group_order.id)
group_order_articles.each do |goa|
# if no unit is received, nothing is to be charged
next if goa.result.to_i == 0
goa_total_gross = goa.result * goa.order_article.price.gross_price
data << [goa.order_article.article.name,
goa.result.to_i,
number_to_currency(goa.order_article.price.gross_price),
number_to_currency(goa.total_price)]
total_gross += goa_total_gross
end
table data, position: :left, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
table.header = true
table.position = :center
table.cells.border_width = 1
table.cells.border_color = '666666'
table.row(0).column(1).width = 40
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(1..6).align = :right
end
move_down 5
sum = []
sum << [nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay'), number_to_currency(total_gross)]
# table for sum
indent(200) do
table sum, position: :center, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
sum.length.times do |count|
table.row(count).columns(0..3).borders = []
end
table.row(sum.length - 1).columns(0..2).borders = []
table.row(sum.length - 1).border_bottom_width = 2
table.row(sum.length - 1).columns(3).borders = [:bottom]
end
end
move_down 25
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
move_down 10
end
def body_with_vat
total_gross = 0 total_gross = 0
total_net = 0 total_net = 0
# Articles # Articles
@ -71,7 +129,6 @@ class GroupOrderInvoicePdf < RenderPDF
tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage
tax_hash_gross = Hash.new(0) # same here with gross prices tax_hash_gross = Hash.new(0) # same here with gross prices
group_order = GroupOrder.find(@options[:group_order].id)
marge = FoodsoftConfig[:price_markup] marge = FoodsoftConfig[:price_markup]
# data table looks different when price_markup > 0 # data table looks different when price_markup > 0
@ -80,7 +137,7 @@ class GroupOrderInvoicePdf < RenderPDF
else else
[I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)]
end end
goa_tax_hash = GroupOrderArticle.where(group_order_id: group_order.id).find_each.group_by { |oat| oat.order_article.price.tax } goa_tax_hash = GroupOrderArticle.where(group_order_id: @group_order.id).find_each.group_by { |oat| oat.order_article.price.tax }
goa_tax_hash.each do |tax, group_order_articles| goa_tax_hash.each do |tax, group_order_articles|
group_order_articles.each do |goa| group_order_articles.each do |goa|
# if no unit is received, nothing is to be charged # if no unit is received, nothing is to be charged
@ -136,6 +193,10 @@ class GroupOrderInvoicePdf < RenderPDF
table.row(sum.length - 1).columns(5).borders = [:bottom] table.row(sum.length - 1).columns(5).borders = [:bottom]
end end
move_down 15 if(FoodsoftConfig[:group_order_invoices][:vat_exempt])
move_down 15
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
end
move_down 10
end end
end end

View file

@ -13,9 +13,11 @@
= config_input form, :charge_members_manually, as: :boolean = config_input form, :charge_members_manually, as: :boolean
= config_input form, :use_iban, as: :boolean = config_input form, :use_iban, as: :boolean
= config_input form, :use_self_service, as: :boolean = config_input form, :use_self_service, as: :boolean
%h4= t '.group_order_invoices'
= form.fields_for :group_order_invoices do |field| = form.fields_for :group_order_invoices do |field|
= config_input field, :use, as: :boolean = config_input field, :use_automatic_invoices, as: :boolean
= config_input field, :payment_method, as: :string, input_html: {class: 'input-mini'} = config_input field, :vat_exempt, as: :boolean
= config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'}
%h4= t '.schedule_title' %h4= t '.schedule_title'
= form.simple_fields_for :order_schedule do |fields| = form.simple_fields_for :order_schedule do |fields|

View file

@ -21,7 +21,7 @@
%td= show_user(order.updated_by) %td= show_user(order.updated_by)
%td{id: "generate-invoice#{order.id}"} %td{id: "generate-invoice#{order.id}"}
- if order.closed? - if order.closed?
-if FoodsoftConfig[:contact][:tax_number] -if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present?
= render :partial => 'group_order_invoices/links', locals:{order: order} = render :partial => 'group_order_invoices/links', locals:{order: order}
-else -else
= I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set') = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set')

View file

@ -1,11 +1,12 @@
.row - order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go|
- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| .row
.row .column.small-3
= label_tag go.ordergroup.name = label_tag go.ordergroup.name
- if go.group_order_invoice - if go.group_order_invoice
.column.small-3
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-small' = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-small'
.column.small-3
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true = link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true
- else - else
= button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: order.id}, remote: true = button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: order.id}, remote: true
%br

View file

@ -304,6 +304,7 @@ de:
emails_title: E-Mails versenden emails_title: E-Mails versenden
tab_payment: tab_payment:
schedule_title: Bestellschema schedule_title: Bestellschema
group_order_invoices: Bestellgruppenrechnungen
tab_security: tab_security:
default_roles_title: Zugriff auf default_roles_title: Zugriff auf
default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche. default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche.
@ -581,6 +582,10 @@ de:
email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen. email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen.
email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest. email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest.
email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden. email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden.
group_order_invoices:
use_automativ_go_invoices: Es werden auf die Bestellgruppen zugeschnittene Rechnungen für die jeweilige Bestellung beim Klicken auf "abrechnen" an alle Bestellgruppenmitglieder per Mail versendet.
payment_method: Zahlungsart wird auf der Bestellgruppenrechnung deklariert
vat_exempt: Eine Auflistung der Rechnungsartikel erfolgt ohne explizite Ausweisung der MwSt. und die Rechnung erhält den notwendigen Zusatz bzgl. der Kleinunternehmerregelung §19 (FoodCoop Marge ebenfalls nicht in Rechnung enthalten)
help_url: Link zur Dokumentationsseite help_url: Link zur Dokumentationsseite
homepage: Webseite der Foodcoop homepage: Webseite der Foodcoop
ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat. ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat.
@ -608,8 +613,6 @@ de:
tolerance_is_costly: Eine möglichst große Menge im Rahmen der Tolerenz bestellen. Wenn dies nicht aktiviert ist, wird im Rahmen der Toleranz nur so viel bestellt, dass damit komplette Einheiten (Boxen) bestellt werden können. Die Option wirkt sich auch auf die Toleranz des Gesamtpreises einer offenen Mitgliederbestellung aus. tolerance_is_costly: Eine möglichst große Menge im Rahmen der Tolerenz bestellen. Wenn dies nicht aktiviert ist, wird im Rahmen der Toleranz nur so viel bestellt, dass damit komplette Einheiten (Boxen) bestellt werden können. Die Option wirkt sich auch auf die Toleranz des Gesamtpreises einer offenen Mitgliederbestellung aus.
distribution_strategy: Wie bei der Verteilung von Artikeln nach dem Empfangen einer Bestellung vorgegangen werden soll. distribution_strategy: Wie bei der Verteilung von Artikeln nach dem Empfangen einer Bestellung vorgegangen werden soll.
use_apple_points: Wenn das Apfel Punktesystem aktiviert ist, ist es erforderlich, dass Mitglieder Aufgaben erledigen um bestellen zu können. use_apple_points: Wenn das Apfel Punktesystem aktiviert ist, ist es erforderlich, dass Mitglieder Aufgaben erledigen um bestellen zu können.
use_automatic_invoices: Bei der Abrechnung einer Bestellung werden Rechnungen für die einzelnen Bestellgruppenautomatisch per Mail versandt
payment_method: Zahlungsart für Bestellgruppenrechnungen
use_boxfill: Wenn aktiviert, können Benutzer nahe am Ende der Bestellung diese nur mehr so verändern, dass sich die Gesamtsumme erhöht. Dies hilft beim auffüllen der verbleibenden Kisten. Es muss trotzdem noch das Kistenauffülldatum bei der Bestellung gesetzt werden. use_boxfill: Wenn aktiviert, können Benutzer nahe am Ende der Bestellung diese nur mehr so verändern, dass sich die Gesamtsumme erhöht. Dies hilft beim auffüllen der verbleibenden Kisten. Es muss trotzdem noch das Kistenauffülldatum bei der Bestellung gesetzt werden.
use_iban: Zusätzlich Feld für die internationale Kontonummer bei Benutzern und Lieferanten anzeigen use_iban: Zusätzlich Feld für die internationale Kontonummer bei Benutzern und Lieferanten anzeigen
use_nick: Benutzernamen anstatt reale Namen zeigen und verwenden, jeder Benutzer muss dazu einen Benutzernamen (Spitznamen) haben. use_nick: Benutzernamen anstatt reale Namen zeigen und verwenden, jeder Benutzer muss dazu einen Benutzernamen (Spitznamen) haben.
@ -640,6 +643,10 @@ de:
email_from: Absenderadresse email_from: Absenderadresse
email_replyto: Antwortadresse email_replyto: Antwortadresse
email_sender: Senderadresse email_sender: Senderadresse
group_order_invoices:
use_automatic_invoices: Automatisch bei Abrechnung per Mail versenden
payment_method: Zahlungsart
vat_exempt: Diese Foodcoop ist MwSt. befreit
help_url: URL Dokumentation help_url: URL Dokumentation
homepage: Webseite homepage: Webseite
ignore_browser_locale: Browsersprache ignorieren ignore_browser_locale: Browsersprache ignorieren
@ -669,9 +676,6 @@ de:
first_order_first_serve: Zuerst an die verteilen, die zuerst bestellt haben first_order_first_serve: Zuerst an die verteilen, die zuerst bestellt haben
no_automatic_distribution: Keine automatische Verteilung no_automatic_distribution: Keine automatische Verteilung
use_apple_points: Apfelpunkte verwenden use_apple_points: Apfelpunkte verwenden
group_order_invoices:
use: Bestellgruppen Rechnungen automatisch bei Abrechnung einer Bestellung versenden
payment_method: Zahlungsart für Bestellgruppenrechnungen
use_boxfill: Kistenauffüllphase use_boxfill: Kistenauffüllphase
use_iban: IBAN verwenden use_iban: IBAN verwenden
use_nick: Benutzernamen verwenden use_nick: Benutzernamen verwenden
@ -740,13 +744,20 @@ de:
contact_address: 'Adresse : %{contact_address}' contact_address: 'Adresse : %{contact_address}'
name: Bestellgruppe %{ordergroup} name: Bestellgruppe %{ordergroup}
payment_method: 'Zahlungsart: %{payment_method}' payment_method: 'Zahlungsart: %{payment_method}'
sum_to_pay: Zu zahlen gesamt
sum_to_pay_net: Zu zahlen gesamt (netto) sum_to_pay_net: Zu zahlen gesamt (netto)
sum_to_pay_gross: Zu zahlen gesamt (brutto) sum_to_pay_gross: Zu zahlen gesamt (brutto)
small_business_regulation: Als Kleinunternehmer*in im Sinne von §19 Abs. 1 Umsatzsteuergesetz (UStG) wird keine Umsatzsteuer berechnet.
table_headline: 'Für die Bestellung fallen folgende Posten an:' table_headline: 'Für die Bestellung fallen folgende Posten an:'
tax_excluded: exkl. MwSt. tax_excluded: exkl. MwSt.
tax_included: zzgl. Gesamtsumme MwSt. %{tax}% tax_included: zzgl. Gesamtsumme MwSt. %{tax}%
tax_number: 'Steuernummer: %{number}' tax_number: 'Steuernummer: %{number}'
title: Rechnung für die Bestellung bei %{supplier} title: Rechnung für die Bestellung bei %{supplier}
vat_exempt_rows:
- Name
- Anzahl
- Einzelpreis
- Artikel Gesamtpreis
no_price_markup_rows: no_price_markup_rows:
- Name - Name
- Anzahl - Anzahl

View file

@ -304,6 +304,7 @@ en:
emails_title: Sending email emails_title: Sending email
tab_payment: tab_payment:
schedule_title: Ordering schedule schedule_title: Ordering schedule
group_order_invoices: Group order invoices
tab_security: tab_security:
default_roles_title: Access to default_roles_title: Access to
default_roles_paragraph: By default every member of the foodcoop has access to the following areas. default_roles_paragraph: By default every member of the foodcoop has access to the following areas.
@ -582,6 +583,9 @@ en:
email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address. email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address.
email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above. email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above.
email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain. email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain.
use_automatic_invoices: A listing of the invoice items is made without explicit display of VAT and the invoice receives the necessary addition regarding the small business regulation §19 (applies to Germany)
payment_method: Payment type is declared on the order group invoice
vat_exempt: A listing of the invoice items is made without explicit display of VAT and the invoice contains the necessary addition regarding the German Kleinunternehmerregelung §19 UStG (attention! FoodCoop marge not included in nvoice).
help_url: Documentation website. help_url: Documentation website.
homepage: Website of your foodcoop. homepage: Website of your foodcoop.
ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet. ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet.
@ -671,8 +675,9 @@ en:
no_automatic_distribution: No automatic distribution no_automatic_distribution: No automatic distribution
use_apple_points: Apple points use_apple_points: Apple points
group_order_invoices: group_order_invoices:
use: Automatically send group order invoices when an order is settled use_automatic_invoices: Send automatically via mail after oder settlement
payment_method: Payment method for group order invoices payment_method: Payment method
vat_exempt: This foodcoopis VAT exempt
use_boxfill: Box-fill phase use_boxfill: Box-fill phase
use_iban: Use IBAN use_iban: Use IBAN
use_nick: Use nicknames use_nick: Use nicknames
@ -742,6 +747,8 @@ en:
invoice_number: 'Invoice number: %{invoice_number}' invoice_number: 'Invoice number: %{invoice_number}'
markup_included: incl Foodcoop Marge on gross price %{marge}% markup_included: incl Foodcoop Marge on gross price %{marge}%
payment_method: 'Payment_method: %{payment_method}' payment_method: 'Payment_method: %{payment_method}'
small_business_regulation: As a small entrepreneur in the sense of §19 para. 1 of the Umsatzsteuergesetz (UStG), no value added tax is charged.
sum_to_pay: Total sum
sum_to_pay_net: Total sum (net) sum_to_pay_net: Total sum (net)
sum_to_pay_gross: Total sum (gross) sum_to_pay_gross: Total sum (gross)
table_headline: 'The following items will be charged for the order:' table_headline: 'The following items will be charged for the order:'
@ -749,16 +756,21 @@ en:
tax_included: incl. VAT %{tax}% tax_included: incl. VAT %{tax}%
tax_number: 'Tax number: %{number}' tax_number: 'Tax number: %{number}'
title: Invoice for order at %{supplier} title: Invoice for order at %{supplier}
vat_exempt_rows:
- Name
- Quantity
- Unit price
- Total price
no_price_markup_rows: no_price_markup_rows:
- Name - Name
- Amount - Quantity
- Unit price (net) - Unit price (net)
- Total price (net) - Total price (net)
- VAT - VAT
- Total price (gross) - Total price (gross)
price_markup_rows: price_markup_rows:
- Name - Name
- Amount - Quantity
- Unit price (net) - Unit price (net)
- Total price (net) - Total price (net)
- VAT - VAT

View file

@ -4,7 +4,7 @@ feature GroupOrderInvoice, js: true do
let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] }
let(:article) { create :article, unit_quantity: 1 } let(:article) { create :article, unit_quantity: 1 }
let(:order) { create :order, supplier: article.supplier, article_ids: [article.id], ends: Time.now } # need to ref article let(:order) { create :order, supplier: article.supplier, article_ids: [article.id], ends: Time.now } # need to ref article
let(:go) { create :group_order, order: order } let(:go) { create :group_order, order: order}
let(:oa) { order.order_articles.find_by_article_id(article.id) } let(:oa) { order.order_articles.find_by_article_id(article.id) }
let(:ftt) { create :financial_transaction_type } let(:ftt) { create :financial_transaction_type }
let(:goa) { create :group_order_article, group_order: go, order_article: oa } let(:goa) { create :group_order_article, group_order: go, order_article: oa }
@ -27,23 +27,14 @@ feature GroupOrderInvoice, js: true do
goa.update_quantities 2, 0 goa.update_quantities 2, 0
oa.update_results! oa.update_results!
order.reload order.reload
FoodsoftConfig[:group_order_invoices] = { use: true } FoodsoftConfig[:group_order_invoices] = { use_automatic_invoices: true }
FoodsoftConfig[:contact][:tax_number] = 12_345_678 FoodsoftConfig[:contact][:tax_number] = 12_345_678
visit confirm_finance_order_path(id: order.id, type: ftt) visit confirm_finance_order_path(id: order.id, type: ftt)
expect(page).to have_selector(:link_or_button, I18n.t('finance.balancing.confirm.clear')) expect(page).to have_selector(:link_or_button, I18n.t('finance.balancing.confirm.clear'))
click_link_or_button I18n.t('finance.balancing.confirm.clear') click_link_or_button I18n.t('finance.balancing.confirm.clear')
expect(NotifyGroupOrderInvoiceJob).to have_been_enqueued expect(NotifyGroupOrderInvoiceJob).to have_been_enqueued
end end
it 'does not generate Group Order Invoice when order is closed if tax_number not set' do
goa.update_quantities 2, 0
oa.update_results!
order.update!(state: 'closed')
order.reload
visit finance_order_index_path
expect(page).to have_content(I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set'))
end
it 'generates Group Order Invoice when order is closed if tax_number is set' do it 'generates Group Order Invoice when order is closed if tax_number is set' do
goa.update_quantities 2, 0 goa.update_quantities 2, 0
oa.update_results! oa.update_results!
@ -54,4 +45,13 @@ feature GroupOrderInvoice, js: true do
click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate') click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate')
expect(GroupOrderInvoice.all.count).to eq(1) expect(GroupOrderInvoice.all.count).to eq(1)
end end
it 'does not generate Group Order Invoice when order is closed if tax_number not set' do
goa.update_quantities 2, 0
oa.update_results!
order.update!(state: 'closed')
order.reload
visit finance_order_index_path
expect(page).to have_content(I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set'))
end
end end