From 0fa696cfdd2d5303830cf2db48ae6e60ca2516e3 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:48:06 +0100 Subject: [PATCH 01/18] add groupo order invoice and relation to group order --- .../group_order_invoices_controller.rb | 38 ++++++++++++ app/models/group_order.rb | 1 + app/models/group_order_invoice.rb | 60 +++++++++++++++++++ ...11208142719_create_group_order_invoices.rb | 13 ++++ 4 files changed, 112 insertions(+) create mode 100644 app/controllers/group_order_invoices_controller.rb create mode 100644 app/models/group_order_invoice.rb create mode 100644 db/migrate/20211208142719_create_group_order_invoices.rb diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb new file mode 100644 index 00000000..bf6d559f --- /dev/null +++ b/app/controllers/group_order_invoices_controller.rb @@ -0,0 +1,38 @@ +class GroupOrderInvoicesController < ApplicationController + include Concerns::SendGroupOrderInvoicePdf + + def show + @group_order_invoice = GroupOrderInvoice.find(params[:id]) + if FoodsoftConfig[:contact][:tax_number] + respond_to do |format| + format.pdf do + send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number] + end + end + else raise RecordInvalid + redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => "#{error} " + I18n.t('errors.check_tax_number')) + end + end + + def destroy + goi = GroupOrderInvoice.find(params[:id]) + @order = goi.group_order.order + goi.destroy + respond_to do |format| + format.js + format.json { head :no_content } + end + end + + def create + go = GroupOrder.find(params[:group_order]) + @order = go.order + goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + respond_to do |format| + format.js + end + redirect_back fallback_location: root_path + rescue => error + redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => error) + end +end \ No newline at end of file diff --git a/app/models/group_order.rb b/app/models/group_order.rb index c789ef4e..3e74754c 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -9,6 +9,7 @@ class GroupOrder < ApplicationRecord has_many :group_order_articles, :dependent => :destroy has_many :order_articles, :through => :group_order_articles has_one :financial_transaction + has_one :group_order_invoice belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id' validates_presence_of :order_id diff --git a/app/models/group_order_invoice.rb b/app/models/group_order_invoice.rb new file mode 100644 index 00000000..f39b4b2a --- /dev/null +++ b/app/models/group_order_invoice.rb @@ -0,0 +1,60 @@ +class GroupOrderInvoice < ApplicationRecord + + belongs_to :group_order + validates_presence_of :group_order + validates_uniqueness_of :invoice_number + validate :tax_number_set + after_initialize :init, unless: :persisted? + + def generate_invoice_number(count) + trailing_number = count.to_s.rjust(4, '0') + unless GroupOrderInvoice.find_by(invoice_number: Time.now.strftime("%Y%m%d") + trailing_number) + Time.now.strftime("%Y%m%d") + trailing_number + else + generate_invoice_number(count.to_i + 1) + end + end + + def tax_number_set + if FoodsoftConfig[:contact][:tax_number].blank? + errors.add(:group_order_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt") + end + end + + def init + self.invoice_number = generate_invoice_number(1) unless self.invoice_number + self.invoice_date = Time.now unless self.invoice_date + self.payment_method = FoodsoftConfig[:group_order_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.group_order_invoice.payment_method') unless self.payment_method + end + + def name + I18n.t('activerecord.attributes.group_order_invoice.name') + "_#{invoice_number}" + end + + def load_data_for_invoice + invoice_data = {} + order = group_order.order + invoice_data[:supplier] = order.supplier.name + invoice_data[:ordergroup] = group_order.ordergroup + invoice_data[:group_order] = group_order + invoice_data[:invoice_number] = invoice_number + invoice_data[:invoice_date] = invoice_date + invoice_data[:tax_number] = FoodsoftConfig[:contact][:tax_number] + invoice_data[:payment_method] = payment_method + invoice_data[:order_articles] = {} + group_order.order_articles.each do |order_article| + # Get the result of last time ordering, if possible + goa = group_order.group_order_articles.detect { |goa| goa.order_article_id == order_article.id } + + # Build hash with relevant data + invoice_data[:order_articles][order_article.id] = { + :price => order_article.article.fc_price, + :quantity => (goa ? goa.quantity : 0), + :total_price => (goa ? goa.total_price : 0), + :tax => order_article.article.tax + } + end + invoice_data + + end +end diff --git a/db/migrate/20211208142719_create_group_order_invoices.rb b/db/migrate/20211208142719_create_group_order_invoices.rb new file mode 100644 index 00000000..b0aa13f7 --- /dev/null +++ b/db/migrate/20211208142719_create_group_order_invoices.rb @@ -0,0 +1,13 @@ +class CreateGroupOrderInvoices < ActiveRecord::Migration[5.2] + def change + create_table :group_order_invoices do |t| + t.integer :group_order_id + t.bigint :invoice_number, unique: true, limit: 8 + t.date :invoice_date + t.string :payment_method + + t.timestamps + end + add_index :group_order_invoices, :group_order_id, unique: true + end +end From 817c680c28f42aa51c02d7267dd0213e45e7ec64 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:50:06 +0100 Subject: [PATCH 02/18] add mailer relevant files, add pdf relevant files and confgurations for invoice generation and sending --- .../concerns/send_group_order_invoice_pdf.rb | 12 ++ app/documents/group_order_invoice_pdf.rb | 142 ++++++++++++++++++ app/jobs/notify_group_order_invoice_job.rb | 10 ++ app/mailers/mailer.rb | 18 +++ .../mailer/group_order_invoice.text.haml | 1 + 5 files changed, 183 insertions(+) create mode 100644 app/controllers/concerns/send_group_order_invoice_pdf.rb create mode 100644 app/documents/group_order_invoice_pdf.rb create mode 100644 app/jobs/notify_group_order_invoice_job.rb create mode 100644 app/views/mailer/group_order_invoice.text.haml diff --git a/app/controllers/concerns/send_group_order_invoice_pdf.rb b/app/controllers/concerns/send_group_order_invoice_pdf.rb new file mode 100644 index 00000000..913a4347 --- /dev/null +++ b/app/controllers/concerns/send_group_order_invoice_pdf.rb @@ -0,0 +1,12 @@ +module Concerns::SendGroupOrderInvoicePdf + extend ActiveSupport::Concern + + protected + + def send_group_order_invoice_pdf group_order_invoice + invoice_data = group_order_invoice.load_data_for_invoice + invoice_data[:title] = "Rechnung für " + invoice_data[:supplier] # TODO Internationalise und load_data rausschmeißen + pdf = GroupOrderInvoicePdf.new group_order_invoice.load_data_for_invoice + send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf' + end +end diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb new file mode 100644 index 00000000..13856330 --- /dev/null +++ b/app/documents/group_order_invoice_pdf.rb @@ -0,0 +1,142 @@ +class GroupOrderInvoicePdf < RenderPDF + def filename + I18n.t('documents.group_order_invoice_pdf.filename', :number => @options[:invoice_number]) + '.pdf' + end + + def title + I18n.t('documents.group_order_invoice_pdf.title', :supplier => @options[:supplier]) + end + + def body + contact = FoodsoftConfig[:contact].symbolize_keys + ordergroup = @options[:ordergroup] + + # From paragraph + bounding_box [margin_box.right - 200, margin_box.top-20], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoicer') + move_down 7 + text FoodsoftConfig[:name], size: fontsize(9), align: :left + move_down 5 + text contact[:street], size: fontsize(9), align: :left + move_down 5 + text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :left + move_down 5 + unless contact[:phone].blank? + text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :left + move_down 5 + end + unless contact[:email].blank? + text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :left + end + move_down 5 + text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left + end + + + # Receiving Ordergroup + bounding_box [margin_box.left, margin_box.top - 20], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoicee') + move_down 7 + text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s ), size: fontsize(9) + move_down 5 + if ordergroup.contact_address + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s ), size: fontsize(9) + move_down 5 + end + if ordergroup.contact_phone + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s ), size: fontsize(9) + move_down 5 + end + end + + #invoice Date and nnvoice number + bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left + move_down 5 + text I18n.t('documents.group_order_invoice_pdf.invoice_number', invoice_number: @options[:invoice_number]), align: :left + end + + move_down 15 + + # kind of the "body" of the invoice + text I18n.t('documents.group_order_invoice_pdf.payment_method', payment_method: @options[:payment_method]) + move_down 15 + text I18n.t('documents.group_order_invoice_pdf.table_headline') + move_down 5 + + #------------- Table Data ----------------------- + total_gross = 0 + total_net = 0 + # Articles + + 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 + + group_order = GroupOrder.find(@options[:group_order].id) + marge = FoodsoftConfig[:price_markup] + + # data table looks different when price_markup > 0 + if marge == 0 + data = [I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')] + else + data = [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] + 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.each do |tax, group_order_articles| + group_order_articles.each do |goa| + # if no unit is received, nothing is to be charged + next if goa.result.to_i == 0 + order_article = goa.order_article + goa_total_net = goa.result * order_article.price.price + goa_total_gross = goa.result * order_article.price.gross_price + data << [order_article.article.name, + goa.result.to_i, + number_to_currency(order_article.price.price), + number_to_currency(goa_total_net), + tax.to_s + '%', + number_to_currency(goa.total_price)] + tax_hash_net[tax.to_i] += goa_total_net + tax_hash_gross[tax.to_i] += goa_total_gross + total_net += goa_total_net + total_gross += goa_total_gross + end + end + + # Two separate tables for sum and individual data + # article information + data + table data, 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 + + sum = [] + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_net'), number_to_currency(total_net)] + tax_hash_net.keys.each do |tax| + sum << [nil,nil,nil,nil, I18n.t('documents.group_order_invoice_pdf.tax_included', tax: tax), number_to_currency(tax_hash_gross[tax] - tax_hash_net[tax])] + end + unless marge == 0 + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.markup_included', marge: marge), number_to_currency(total_gross * marge/100.0)] + end + end_sum = total_gross * (1 + marge/100.0) + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(end_sum)] + # table for sum + table sum, position: :right, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + sum.length.times do |count| + table.row(count).columns(0..5).borders = [] + end + table.row(sum.length-1).columns(0..4).borders = [] + table.row(sum.length-1).border_bottom_width = 2 + table.row(sum.length-1).columns(5).borders = [:bottom] + + end + + move_down 15 + end +end diff --git a/app/jobs/notify_group_order_invoice_job.rb b/app/jobs/notify_group_order_invoice_job.rb new file mode 100644 index 00000000..1a17fe9a --- /dev/null +++ b/app/jobs/notify_group_order_invoice_job.rb @@ -0,0 +1,10 @@ +class NotifyGroupOrderInvoiceJob < ApplicationJob + def perform(group_order_invoice) + ordergroup = group_order_invoice.group_order.ordergroup + ordergroup.users.each do |user| + Mailer.deliver_now_with_user_locale user do + Mailer.group_order_invoice(group_order_invoice, user) + end + end + end +end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 40a8d1de..ae99e08c 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -51,6 +51,19 @@ class Mailer < ActionMailer::Base subject: I18n.t('mailer.welcome.subject') end + # Sends automatically generated invoicesfor group orders to ordergroup members + def group_order_invoice(group_order_invoice, user) + @user = user + @group_order_invoice = group_order_invoice + @group_order = group_order_invoice.group_order + @supplier = @group_order.order.supplier.name + @group = @group_order.ordergroup + add_group_order_invoice_attachments(group_order_invoice) + mail to: user, + subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier) + end + + # Sends order result for specific Ordergroup def order_result(user, group_order) @order = group_order.order @@ -168,6 +181,11 @@ class Mailer < ActionMailer::Base attachments['order.csv'] = OrderCsv.new(order, options).to_csv end + def add_group_order_invoice_attachments(group_order_invoice) + attachment_name = group_order_invoice.name + '.pdf' + attachments[attachment_name] = GroupOrderInvoicePdf.new(group_order_invoice.load_data_for_invoice).to_pdf + end + # separate method to allow plugins to mess with the text def additonal_welcome_text(user) end diff --git a/app/views/mailer/group_order_invoice.text.haml b/app/views/mailer/group_order_invoice.text.haml new file mode 100644 index 00000000..75948fbe --- /dev/null +++ b/app/views/mailer/group_order_invoice.text.haml @@ -0,0 +1 @@ += raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name] From 50017fefa833e0dd8b6e9a288d9e4e264578275c Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:51:44 +0100 Subject: [PATCH 03/18] add functionality in views and controllers for generation of group order invoices --- .../finance/balancing_controller.rb | 18 +++++++++++++++--- app/views/finance/balancing/_orders.html.haml | 10 ++++++++++ .../group_order_invoices/_links.html.haml | 11 +++++++++++ app/views/group_order_invoices/create.js.erb | 1 + app/views/group_order_invoices/destroy.js.erb | 1 + app/views/group_order_invoices/show.html.haml | 0 app/views/group_order_invoices/show.pdf.prawn | 1 + config/routes.rb | 3 ++- 8 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 app/views/group_order_invoices/_links.html.haml create mode 100644 app/views/group_order_invoices/create.js.erb create mode 100644 app/views/group_order_invoices/destroy.js.erb create mode 100644 app/views/group_order_invoices/show.html.haml create mode 100644 app/views/group_order_invoices/show.pdf.prawn diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 09c109f8..870f7159 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -81,9 +81,21 @@ class Finance::BalancingController < Finance::BaseController @order = Order.find(params[:id]) @type = FinancialTransactionType.find_by_id(params.permit(:type)[:type]) @order.close!(@current_user, @type) - redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice') - rescue => error - redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message) + note = t('finance.balancing.close.notice') + if @order.closed? + if FoodsoftConfig[:group_order_invoices]&.[](:use) && FoodsoftConfig[:contact]&.[](:tax_number) + @order.group_orders.each do |go| + goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + if goi.save! + NotifyGroupOrderInvoiceJob.perform_later(goi) + note = t('finance.balancing.close.notice_mail') + end + end + end + end + redirect_to finance_order_index_url, notice: note + rescue => error + redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: t('finance.balancing.close.alert', message: error.message) end # Close the order directly, without automaticly updating ordergroups account balances diff --git a/app/views/finance/balancing/_orders.html.haml b/app/views/finance/balancing/_orders.html.haml index 3f20d850..addbf930 100644 --- a/app/views/finance/balancing/_orders.html.haml +++ b/app/views/finance/balancing/_orders.html.haml @@ -9,6 +9,8 @@ %th= t('.end') %th= t('.state') %th= heading_helper Order, :updated_by + %th= heading_helper GroupOrderInvoice, :name + %th %th %tbody - @orders.each do |order| @@ -17,6 +19,14 @@ %td=h format_time(order.ends) unless order.ends.nil? %td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended') %td= show_user(order.updated_by) + %td{id: "generate-invoice#{order.id}"} + - if order.closed? + -if FoodsoftConfig[:contact][:tax_number] + = render :partial => 'group_order_invoices/links', locals:{order: order} + -else + = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set') + - else + = t('orders.index.not_closed') %td - unless order.closed? - if current_user.role_orders? diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml new file mode 100644 index 00000000..0d270756 --- /dev/null +++ b/app/views/group_order_invoices/_links.html.haml @@ -0,0 +1,11 @@ +.row + - order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| + .row + = label_tag go.ordergroup.name + - if go.group_order_invoice + = 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.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true + - 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 + %br + diff --git a/app/views/group_order_invoices/create.js.erb b/app/views/group_order_invoices/create.js.erb new file mode 100644 index 00000000..5a43e85d --- /dev/null +++ b/app/views/group_order_invoices/create.js.erb @@ -0,0 +1 @@ +$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>"); diff --git a/app/views/group_order_invoices/destroy.js.erb b/app/views/group_order_invoices/destroy.js.erb new file mode 100644 index 00000000..30ce5985 --- /dev/null +++ b/app/views/group_order_invoices/destroy.js.erb @@ -0,0 +1 @@ +$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>"); \ No newline at end of file diff --git a/app/views/group_order_invoices/show.html.haml b/app/views/group_order_invoices/show.html.haml new file mode 100644 index 00000000..e69de29b diff --git a/app/views/group_order_invoices/show.pdf.prawn b/app/views/group_order_invoices/show.pdf.prawn new file mode 100644 index 00000000..a280e3b5 --- /dev/null +++ b/app/views/group_order_invoices/show.pdf.prawn @@ -0,0 +1 @@ +pdf.text "Hello World" \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index d7ec44a7..b0f5c300 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -139,7 +139,7 @@ Rails.application.routes.draw do end end end - + resources :group_order_invoices resources :article_categories ########### Finance @@ -172,6 +172,7 @@ Rails.application.routes.draw do get :unpaid, on: :collection end + resources :links, controller: 'financial_links', only: [:create, :show] do collection do get :incomplete From eadfbd596916c82c67f3562f732a0980124d5154 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:52:04 +0100 Subject: [PATCH 04/18] add locales --- config/locales/de.yml | 61 +++++++++++++++++++++++++++++++++++++++++++ config/locales/en.yml | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/config/locales/de.yml b/config/locales/de.yml index b50d4f8f..898218ef 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -84,6 +84,14 @@ de: tolerance: Toleranz total_price: Summe unit_price: Preis/Einheit + group_order_invoice: + name: Bestellgruppenrechnung + links: + delete: Rechnung löschen + generate: Rechnung erzeugen + download: Rechnung herunterladen + payment_method: Guthaben + tax_number_not_set: Steuernummer in den Einstellungen nicht gesetzt invoice: amount: Betrag attachment: Anhang @@ -600,6 +608,8 @@ 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. 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_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_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. @@ -615,6 +625,7 @@ de: phone: Telefon street: Straße zip_code: Postleitzahl + tax_number: Steuernummer currency_space: Leerzeichen hinzufügen currency_unit: Währung custom_css: Angepasstes CSS @@ -658,6 +669,9 @@ de: first_order_first_serve: Zuerst an die verteilen, die zuerst bestellt haben no_automatic_distribution: Keine automatische Verteilung 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_iban: IBAN verwenden use_nick: Benutzernamen verwenden @@ -714,6 +728,39 @@ de: update: notice: Lieferung wurde aktualisiert. documents: + group_order_invoice_pdf: + filename: Rechnung %{number} + invoicer: Rechnungsteller*in + invoicee: Rechnungsempfänger*in + invoice_date: 'Rechnungsdatum: %{invoice_date}' + invoice_number: 'Rechnungsnummer: %{invoice_number}' + markup_included: zzgl. Foodcoop Marge auf brutto Preis %{marge}% + ordergroup: + contact_phone: 'Telefonnummer: %{contact_phone}' + contact_address: 'Adresse : %{contact_address}' + name: Bestellgruppe %{ordergroup} + payment_method: 'Zahlungsart: %{payment_method}' + sum_to_pay_net: Zu zahlen gesamt (netto) + sum_to_pay_gross: Zu zahlen gesamt (brutto) + table_headline: 'Für die Bestellung fallen folgende Posten an:' + tax_excluded: exkl. MwSt. + tax_included: zzgl. Gesamtsumme MwSt. %{tax}% + tax_number: 'Steuernummer: %{number}' + title: Rechnung für die Bestellung bei %{supplier} + no_price_markup_rows: + - Name + - Anzahl + - Einzelpreis (netto) + - Artikel Gesamtpreis (netto) + - MwSt. + - Artikel Gesamtpreis (brutto) + price_markup_rows: + - Name + - Anzahl + - Einzelpreis (netto) + - Artikel Gesamtpreis (netto) + - MwSt. + - Artikel Gesamtpreis (brutto) inkl. Foodcoopmarge %{marge}% order_by_articles: filename: Bestellung %{name}-%{date} - Artikelsortierung title: 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}' @@ -737,6 +784,7 @@ de: heading: Artikelübersicht (%{count}) title: 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}' errors: + check_tax_number: Überprüft, ob die Steuernummer der Foodcoop richtig gesetzt ist general: Ein Problem ist aufgetreten. general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen. general_msg: 'Ein Fehler ist aufgetreten: %{msg}' @@ -760,6 +808,8 @@ de: close: alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}' notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. + notice_mail: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. Außerdem wurden automatisch Rechnungen an die Bestellgruppenmitglieder geschickt. + settings_not_set: Keine Emails mit Rechnungen versendet. Bitte überprüfe die Einstellungen. close_all_direct_with_invoice: notice: 'Es wurden %{count} Bestellung abgerechnet.' close_direct: @@ -824,6 +874,7 @@ de: ended: beendet name: Lieferantin no_closed_orders: Derzeit gibt es keine beendeten Bestellungen. + state: Status summary: changed: Daten wurden verändert! @@ -1235,6 +1286,15 @@ de: feedback: header: "%{user} schrieb am %{date}:" subject: Feedback zur Foodsoft + group_order_invoice: + subject: Bestellgruppenrechnung für %{group} bei %{supplier} + text: | + Liebe Bestellgruppe %{group}, + + Die Sammelbestellung bei %{supplier} wurde soeben abgerechnet und für die jeweiligen Bestellgruppen Rechnungen angelegt. + Im Anhang befindet sich daher eure Rechnung. + + Viele Grüße von %{foodcoop} invite: subject: Einladung in die Foodcoop text: | @@ -1467,6 +1527,7 @@ de: orders_finished: Beendet orders_open: Laufend orders_settled: Abgerechnet + not_closed: Bestellung noch nicht abgerechnet title: Bestellungen verwalten model: close_direct_message: Die Bestellung wurde abgechlossen, ohne die Mitgliederkonten zu belasten. diff --git a/config/locales/en.yml b/config/locales/en.yml index 819a2cab..7b947156 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -84,6 +84,14 @@ en: tolerance: Tolerance total_price: Sum unit_price: Price/Unit + group_order_invoice: + name: Group order invoice + links: + delete: delete invoice + generate: generate invoice + download: download invoice + payment_method: Credit + tax_number_not_set: Tax number not set in configs invoice: amount: Amount attachment: Attachment @@ -601,6 +609,8 @@ en: tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order. distribution_strategy: How articles should be distributed after an order has been received. use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering. + use_automatic_invoices: When an order is settled, invoices for the individual order groups are automatically sent by mail + payment_method: Payment Method for group order invoices use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders. use_iban: When enabled, supplier and user provide an additonal field for storing the international bank account number. use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname. @@ -616,6 +626,7 @@ en: phone: Phone street: Street zip_code: Postcode + tax_number: Tax number currency_space: add space currency_unit: Currency custom_css: Custom CSS @@ -659,6 +670,9 @@ en: first_order_first_serve: First distribute to those who ordered first no_automatic_distribution: No automatic distribution use_apple_points: Apple points + group_order_invoices: + use: Automatically send group order invoices when an order is settled + payment_method: Payment method for group order invoices use_boxfill: Box-fill phase use_iban: Use IBAN use_nick: Use nicknames @@ -716,6 +730,39 @@ en: update: notice: Delivery was updated. documents: + group_order_invoice_pdf: + ordergroup: + contact_phone: 'Phone: %{contact_phone}' + contact_address: 'Adress : %{contact_address}' + name: 'Ordergroup: %{ordergroup}' + filename: Invoice %{number} + invoicee: Invoicee + invoicer: Invoicer + invoice_date: 'Invoice date: %{invoice_date}' + invoice_number: 'Invoice number: %{invoice_number}' + markup_included: incl Foodcoop Marge on gross price %{marge}% + payment_method: 'Payment_method: %{payment_method}' + sum_to_pay_net: Total sum (net) + sum_to_pay_gross: Total sum (gross) + table_headline: 'The following items will be charged for the order:' + tax_excluded: excl. MwSt. + tax_included: incl. VAT %{tax}% + tax_number: 'Tax number: %{number}' + title: Invoice for order at %{supplier} + no_price_markup_rows: + - Name + - Amount + - Unit price (net) + - Total price (net) + - VAT + - Total price (gross) + price_markup_rows: + - Name + - Amount + - Unit price (net) + - Total price (net) + - VAT + - Total price (gross) incl. foodcoop margin order_by_articles: filename: Order %{name}-%{date} - by articles title: 'Order sorted by articles: %{name}, closed at %{date}' @@ -739,6 +786,7 @@ en: heading: Article overview (%{count}) title: 'Order sorting matrix: %{name}, closed at %{date}' errors: + check_tax_number: Please check whether the foodcoop's tax number is set correctly. general: A problem has occured. general_again: A problem has occured. Please try again. general_msg: 'A problem has occured: %{msg}' @@ -762,6 +810,7 @@ en: close: alert: 'An error occured while accounting: %{message}' notice: Order was settled succesfully, the balance of the account was updated. + settings_not_set: No emails with invoices sent. Please check the settings. close_all_direct_with_invoice: notice: '%{count} orders have been settled.' close_direct: @@ -1239,6 +1288,15 @@ en: feedback: header: "%{user} wrote at %{date}:" subject: Feedback for Foodsoft + group_order_invoice: + subject: Order group invoice for %{group} at %{supplier} + text: | + Dear order group %{group}, + + The collective order at %{supplier} has just been settled and invoices have been created for the respective order groups. + Attached you will find your invoice. + + Best regards from %{foodcoop} from_via_foodsoft: "%{name} via Foodsoft" invite: subject: Invitation to the Foodcoop @@ -1478,6 +1536,7 @@ en: orders_finished: Closed orders_open: Open orders_settled: Settled + not_closed: Order not yet settled title: Manage orders model: close_direct_message: Order settled without charging member accounts. From 298a476061a715f9a7f394af7bce9e89fe93b042 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:52:37 +0100 Subject: [PATCH 05/18] add tests (integration + model) --- spec/factories/group_order_invoice.rb | 7 +++ spec/integration/group_order_invoices_spec.rb | 61 +++++++++++++++++++ spec/models/group_order_invoice_spec.rb | 61 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 spec/factories/group_order_invoice.rb create mode 100644 spec/integration/group_order_invoices_spec.rb create mode 100644 spec/models/group_order_invoice_spec.rb diff --git a/spec/factories/group_order_invoice.rb b/spec/factories/group_order_invoice.rb new file mode 100644 index 00000000..036a2815 --- /dev/null +++ b/spec/factories/group_order_invoice.rb @@ -0,0 +1,7 @@ +require 'factory_bot' + +FactoryBot.define do + factory :group_order_invoice do + group_order{ create :group_order} + end +end diff --git a/spec/integration/group_order_invoices_spec.rb b/spec/integration/group_order_invoices_spec.rb new file mode 100644 index 00000000..05535d76 --- /dev/null +++ b/spec/integration/group_order_invoices_spec.rb @@ -0,0 +1,61 @@ +require_relative '../spec_helper' + +feature GroupOrderInvoice, js: true do + let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } + 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(:go) { create :group_order, order: order } + let(:oa) { order.order_articles.find_by_article_id(article.id) } + let(:ftt) { create :financial_transaction_type } + let(:goa) { create :group_order_article, group_order: go, order_article: oa } + + + + describe 'trigger process' do + + include ActiveJob::TestHelper + + before { login admin } + after { clear_enqueued_jobs } + + it 'does not enqueue MailerJob when order is settled if tax_number or options not set' do + goa.update_quantities 2, 0 + oa.update_results! + visit confirm_finance_order_path(id: order.id) + click_link_or_button I18n.t('finance.balancing.confirm.clear') + expect(NotifyGroupOrderInvoiceJob).not_to have_been_enqueued + end + + it 'enqueues MailerJob when order is settled if tax_number or options are set' do + goa.update_quantities 2, 0 + oa.update_results! + order.reload + FoodsoftConfig[:group_order_invoices] = { use: true } + FoodsoftConfig[:contact][:tax_number] = 12345678 + visit confirm_finance_order_path(id: order.id, type: ftt) + expect(page).to have_selector(: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 + 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 + goa.update_quantities 2, 0 + oa.update_results! + FoodsoftConfig[:contact][:tax_number] = 12345678 + order.update!(state: 'closed') + order.reload + visit finance_order_index_path + click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate') + expect(GroupOrderInvoice.all.count).to eq(1) + end + end +end \ No newline at end of file diff --git a/spec/models/group_order_invoice_spec.rb b/spec/models/group_order_invoice_spec.rb new file mode 100644 index 00000000..1f3fc559 --- /dev/null +++ b/spec/models/group_order_invoice_spec.rb @@ -0,0 +1,61 @@ +require_relative '../spec_helper' + +describe GroupOrderInvoice do + let(:user) { create :user, groups: [create(:ordergroup)] } + let(:supplier) { create :supplier } + let(:article) { create :article, supplier: supplier } + let(:order){ create :order } + let(:group_order) {create :group_order, order: order, ordergroup: user.ordergroup } + + describe 'erroneous group order invoice' do + let(:goi) { create :group_order_invoice, group_order_id: group_order.id } + + it 'does not create group order invoice if tax_number not set' do + expect{goi}.to raise_error(ActiveRecord::RecordInvalid) + end + + end + + describe 'valid group order invoice' do + before do + FoodsoftConfig[:contact][:tax_number] = 12345678 + end + + invoice_number1 = Time.now.strftime("%Y%m%d") + '0001' + invoice_number2 = Time.now.strftime("%Y%m%d") + '0002' + + let(:user_2) { create :user, groups: [create(:ordergroup)] } + + let(:goi_1) { create :group_order_invoice, group_order_id: group_order.id } + let(:goi_2) { create :group_order_invoice, group_order_id: group_order.id } + + let(:group_order_2) {create :group_order, order: order, ordergroup: user_2.ordergroup } + + let(:goi_3) { create :group_order_invoice, group_order_id: group_order_2.id } + let(:goi_4) { create :group_order_invoice, group_order_id: group_order_2.id, invoice_number: invoice_number1 } + + it 'creates group order invoice if tax_number is set' do + expect(goi_1).to be_valid + end + + it 'sets invoice_number according to date' do + number = Time.now.strftime("%Y%m%d") + '0001' + expect(goi_1.invoice_number).to eq(number.to_i) + end + + it 'fails to create if group_order_id is used multiple times for creation' do + expect(goi_1.group_order.id).to eq(group_order.id) + expect{goi_2}.to raise_error(ActiveRecord::RecordNotUnique) + end + + it 'creates two different group order invoice with different invoice_numbers' do + expect(goi_1.invoice_number).to eq(invoice_number1.to_i) + expect(goi_3.invoice_number).to eq(invoice_number2.to_i) + end + + it 'fails to create two different group order invoice with same invoice_numbers' do + goi_1 + expect{goi_4}.to raise_error(ActiveRecord::RecordInvalid) + end + end +end \ No newline at end of file From 72fa7c1123030fb00fe809975fed077668aae1b1 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:57:04 +0100 Subject: [PATCH 06/18] add finally schema.rb --- db/schema.rb | 98 +++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 44 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 6e85c5d6..11e4a4ce 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,15 +10,15 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_02_05_090257) do +ActiveRecord::Schema.define(version: 2021_12_08_142719) do - create_table "article_categories", id: :integer, force: :cascade do |t| + create_table "article_categories", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.string "description" t.index ["name"], name: "index_article_categories_on_name", unique: true end - create_table "article_prices", id: :integer, force: :cascade do |t| + create_table "article_prices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "article_id", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false @@ -28,7 +28,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["article_id"], name: "index_article_prices_on_article_id" end - create_table "articles", id: :integer, force: :cascade do |t| + create_table "articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.integer "supplier_id", default: 0, null: false t.integer "article_category_id", default: 0, null: false @@ -54,14 +54,14 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["type"], name: "index_articles_on_type" end - create_table "assignments", id: :integer, force: :cascade do |t| + create_table "assignments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "user_id", default: 0, null: false t.integer "task_id", default: 0, null: false t.boolean "accepted", default: false t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true end - create_table "bank_accounts", id: :integer, force: :cascade do |t| + create_table "bank_accounts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "iban" t.string "description" @@ -70,7 +70,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.string "import_continuation_point" end - create_table "bank_transactions", id: :integer, force: :cascade do |t| + create_table "bank_transactions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "bank_account_id", null: false t.string "external_id" t.date "date" @@ -84,7 +84,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id" end - create_table "documents", id: :integer, force: :cascade do |t| + create_table "documents", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name" t.string "mime" t.binary "data", limit: 4294967295 @@ -95,16 +95,16 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["parent_id"], name: "index_documents_on_parent_id" end - create_table "financial_links", id: :integer, force: :cascade do |t| + create_table "financial_links", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.text "note" end - create_table "financial_transaction_classes", id: :integer, force: :cascade do |t| + create_table "financial_transaction_classes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.boolean "ignore_for_account_balance", default: false, null: false end - create_table "financial_transaction_types", id: :integer, force: :cascade do |t| + create_table "financial_transaction_types", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.integer "financial_transaction_class_id", null: false t.string "name_short" @@ -112,7 +112,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["name_short"], name: "index_financial_transaction_types_on_name_short" end - create_table "financial_transactions", id: :integer, force: :cascade do |t| + create_table "financial_transactions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "ordergroup_id" t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false t.text "note", null: false @@ -126,7 +126,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true end - create_table "group_order_article_quantities", id: :integer, force: :cascade do |t| + create_table "group_order_article_quantities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "group_order_article_id", default: 0, null: false t.integer "quantity", default: 0 t.integer "tolerance", default: 0 @@ -134,7 +134,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id" end - create_table "group_order_articles", id: :integer, force: :cascade do |t| + create_table "group_order_articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "group_order_id", default: 0, null: false t.integer "order_article_id", default: 0, null: false t.integer "quantity", default: 0, null: false @@ -147,7 +147,17 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" end - create_table "group_orders", id: :integer, force: :cascade do |t| + create_table "group_order_invoices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| + t.integer "group_order_id" + t.bigint "invoice_number" + t.date "invoice_date" + t.string "payment_method" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["group_order_id"], name: "index_group_order_invoices_on_group_order_id", unique: true + end + + create_table "group_orders", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "ordergroup_id" t.integer "order_id", default: 0, null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false @@ -160,7 +170,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id" end - create_table "groups", id: :integer, force: :cascade do |t| + create_table "groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "type", default: "", null: false t.string "name", default: "", null: false t.string "description" @@ -185,7 +195,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["name"], name: "index_groups_on_name", unique: true end - create_table "invites", id: :integer, force: :cascade do |t| + create_table "invites", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "token", default: "", null: false t.datetime "expires_at", null: false t.integer "group_id", default: 0, null: false @@ -194,7 +204,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["token"], name: "index_invites_on_token" end - create_table "invoices", id: :integer, force: :cascade do |t| + create_table "invoices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.string "number" t.date "date" @@ -212,7 +222,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["supplier_id"], name: "index_invoices_on_supplier_id" end - create_table "links", id: :integer, force: :cascade do |t| + create_table "links", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "url", null: false t.integer "workgroup_id" @@ -220,7 +230,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.string "authorization" end - create_table "mail_delivery_status", id: :integer, force: :cascade do |t| + create_table "mail_delivery_status", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.datetime "created_at" t.string "email", null: false t.string "message", null: false @@ -229,13 +239,13 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["email"], name: "index_mail_delivery_status_on_email" end - create_table "memberships", id: :integer, force: :cascade do |t| + create_table "memberships", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "group_id", default: 0, null: false t.integer "user_id", default: 0, null: false t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true end - create_table "message_recipients", id: :integer, force: :cascade do |t| + create_table "message_recipients", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "message_id", null: false t.integer "user_id", null: false t.integer "email_state", default: 0, null: false @@ -244,7 +254,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at" end - create_table "messages", id: :integer, force: :cascade do |t| + create_table "messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "sender_id" t.string "subject", null: false t.text "body" @@ -256,7 +266,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.binary "received_email", limit: 16777215 end - create_table "oauth_access_grants", id: :integer, force: :cascade do |t| + create_table "oauth_access_grants", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "resource_owner_id", null: false t.integer "application_id", null: false t.string "token", null: false @@ -268,7 +278,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true end - create_table "oauth_access_tokens", id: :integer, force: :cascade do |t| + create_table "oauth_access_tokens", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" t.string "token", null: false @@ -282,7 +292,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true end - create_table "oauth_applications", id: :integer, force: :cascade do |t| + create_table "oauth_applications", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "uid", null: false t.string "secret", null: false @@ -294,7 +304,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true end - create_table "order_articles", id: :integer, force: :cascade do |t| + create_table "order_articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "order_id", default: 0, null: false t.integer "article_id", default: 0, null: false t.integer "quantity", default: 0, null: false @@ -308,7 +318,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["order_id"], name: "index_order_articles_on_order_id" end - create_table "order_comments", id: :integer, force: :cascade do |t| + create_table "order_comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "order_id" t.integer "user_id" t.text "text" @@ -316,7 +326,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["order_id"], name: "index_order_comments_on_order_id" end - create_table "orders", id: :integer, force: :cascade do |t| + create_table "orders", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.text "note" t.datetime "starts" @@ -335,7 +345,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["state"], name: "index_orders_on_state" end - create_table "page_versions", id: :integer, force: :cascade do |t| + create_table "page_versions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "page_id" t.integer "lock_version" t.text "body" @@ -346,7 +356,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["page_id"], name: "index_page_versions_on_page_id" end - create_table "pages", id: :integer, force: :cascade do |t| + create_table "pages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "title" t.text "body" t.string "permalink" @@ -360,20 +370,20 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["title"], name: "index_pages_on_title" end - create_table "periodic_task_groups", id: :integer, force: :cascade do |t| + create_table "periodic_task_groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.date "next_task_date" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "poll_choices", id: :integer, force: :cascade do |t| + create_table "poll_choices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "poll_vote_id", null: false t.integer "choice", null: false t.integer "value", null: false t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true end - create_table "poll_votes", id: :integer, force: :cascade do |t| + create_table "poll_votes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "poll_id", null: false t.integer "user_id", null: false t.integer "ordergroup_id" @@ -383,7 +393,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true end - create_table "polls", id: :integer, force: :cascade do |t| + create_table "polls", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "created_by_user_id", null: false t.string "name", null: false t.text "description" @@ -403,7 +413,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["final_choice"], name: "index_polls_on_final_choice" end - create_table "printer_job_updates", id: :integer, force: :cascade do |t| + create_table "printer_job_updates", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "printer_job_id", null: false t.datetime "created_at", null: false t.string "state", null: false @@ -411,7 +421,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at" end - create_table "printer_jobs", id: :integer, force: :cascade do |t| + create_table "printer_jobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "order_id" t.string "document", null: false t.integer "created_by_user_id", null: false @@ -420,7 +430,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["finished_at"], name: "index_printer_jobs_on_finished_at" end - create_table "settings", id: :integer, force: :cascade do |t| + create_table "settings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "var", null: false t.text "value" t.integer "thing_id" @@ -430,7 +440,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true end - create_table "stock_changes", id: :integer, force: :cascade do |t| + create_table "stock_changes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "stock_event_id" t.integer "order_id" t.integer "stock_article_id" @@ -440,7 +450,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id" end - create_table "stock_events", id: :integer, force: :cascade do |t| + create_table "stock_events", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.integer "supplier_id" t.date "date" t.datetime "created_at" @@ -450,13 +460,13 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["supplier_id"], name: "index_stock_events_on_supplier_id" end - create_table "supplier_categories", id: :integer, force: :cascade do |t| + create_table "supplier_categories", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", null: false t.string "description" t.integer "financial_transaction_class_id" end - create_table "suppliers", id: :integer, force: :cascade do |t| + create_table "suppliers", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.string "address", default: "", null: false t.string "phone", default: "", null: false @@ -478,7 +488,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["name"], name: "index_suppliers_on_name", unique: true end - create_table "tasks", id: :integer, force: :cascade do |t| + create_table "tasks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "name", default: "", null: false t.text "description" t.date "due_date" @@ -495,7 +505,7 @@ ActiveRecord::Schema.define(version: 2021_02_05_090257) do t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id" end - create_table "users", id: :integer, force: :cascade do |t| + create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| t.string "nick" t.string "password_hash", default: "", null: false t.string "password_salt", default: "", null: false From 06eb56acf44cbfb7e2c2a19cfa36a810e1d854c9 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Thu, 23 Dec 2021 01:32:43 +0100 Subject: [PATCH 07/18] add tax_number, payment_method and automatic invoice options to be editable in admin configs --- app/views/admin/configs/_tab_foodcoop.html.haml | 1 + app/views/admin/configs/_tab_payment.html.haml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/app/views/admin/configs/_tab_foodcoop.html.haml b/app/views/admin/configs/_tab_foodcoop.html.haml index 5aea80c2..efea4d81 100644 --- a/app/views/admin/configs/_tab_foodcoop.html.haml +++ b/app/views/admin/configs/_tab_foodcoop.html.haml @@ -7,4 +7,5 @@ = config_input c, :country, as: :string, input_html: {class: 'input-xlarge'} = config_input c, :email, required: true, input_html: {class: 'input-xlarge'} = config_input c, :phone, input_html: {class: 'input-medium'} + = config_input c, :tax_number, input_html: {class: 'input-medium'} = config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'} diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml index 3fd7ca0a..de7ea627 100644 --- a/app/views/admin/configs/_tab_payment.html.haml +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -13,6 +13,9 @@ = config_input form, :charge_members_manually, as: :boolean = config_input form, :use_iban, as: :boolean = config_input form, :use_self_service, as: :boolean += form.fields_for :group_order_invoices do |field| + = config_input field, :use, as: :boolean + = config_input field, :payment_method, as: :string, input_html: {class: 'input-mini'} %h4= t '.schedule_title' = form.simple_fields_for :order_schedule do |fields| From f592b27f923c6d1718054ca0dd195b8ebd7e5f69 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Fri, 24 Dec 2021 13:17:45 +0100 Subject: [PATCH 08/18] fix tiny issue where flash alert not shown --- app/controllers/finance/balancing_controller.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 870f7159..485fe7b7 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -5,7 +5,7 @@ class Finance::BalancingController < Finance::BaseController def new @order = Order.find(params[:order_id]) - flash.now.alert = t('finance.balancing.new.alert') if @order.closed? + flash.now.alert = t('finance.balancing.new.alert') if @order.closed? && flash[:alert].blank? @comments = @order.comments @articles = @order.order_articles.ordered_or_member.includes(:article, :article_price, @@ -81,21 +81,24 @@ class Finance::BalancingController < Finance::BaseController @order = Order.find(params[:id]) @type = FinancialTransactionType.find_by_id(params.permit(:type)[:type]) @order.close!(@current_user, @type) - note = t('finance.balancing.close.notice') + note = t('finance.balancing.close.notice') if @order.closed? - if FoodsoftConfig[:group_order_invoices]&.[](:use) && FoodsoftConfig[:contact]&.[](:tax_number) + alert = t('finance.balancing.close.alert') + if FoodsoftConfig[:group_order_invoices]&.[](:use) @order.group_orders.each do |go| + alert = t('finance.balancing.close.settings_not_set') goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) if goi.save! NotifyGroupOrderInvoiceJob.perform_later(goi) - note = t('finance.balancing.close.notice_mail') + note = t('finance.balancing.close.notice_mail') end end end end + alert ||= t('finance.balancing.close.alert') redirect_to finance_order_index_url, notice: note - rescue => error - redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: t('finance.balancing.close.alert', message: error.message) + rescue => error + redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: alert, msg: error.message end # Close the order directly, without automaticly updating ordergroups account balances From f42845051635df913fe04d62779840f53172ef67 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Fri, 24 Dec 2021 13:31:10 +0100 Subject: [PATCH 09/18] left over todo for naming od pdf file --- app/controllers/concerns/send_group_order_invoice_pdf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/concerns/send_group_order_invoice_pdf.rb b/app/controllers/concerns/send_group_order_invoice_pdf.rb index 913a4347..6444b3ea 100644 --- a/app/controllers/concerns/send_group_order_invoice_pdf.rb +++ b/app/controllers/concerns/send_group_order_invoice_pdf.rb @@ -3,9 +3,9 @@ module Concerns::SendGroupOrderInvoicePdf protected - def send_group_order_invoice_pdf group_order_invoice + def send_group_order_invoice_pdf(group_order_invoice) invoice_data = group_order_invoice.load_data_for_invoice - invoice_data[:title] = "Rechnung für " + invoice_data[:supplier] # TODO Internationalise und load_data rausschmeißen + invoice_data[:title] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier]) pdf = GroupOrderInvoicePdf.new group_order_invoice.load_data_for_invoice send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf' end From 303f9029d3205375b768ea9651a40744abcaaa12 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Fri, 24 Dec 2021 13:31:46 +0100 Subject: [PATCH 10/18] locales for prev commits --- config/locales/de.yml | 4 ++-- config/locales/en.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/locales/de.yml b/config/locales/de.yml index 898218ef..1512514a 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -729,7 +729,7 @@ de: notice: Lieferung wurde aktualisiert. documents: group_order_invoice_pdf: - filename: Rechnung %{number} + filename: Rechnung%{number} invoicer: Rechnungsteller*in invoicee: Rechnungsempfänger*in invoice_date: 'Rechnungsdatum: %{invoice_date}' @@ -809,7 +809,7 @@ de: alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}' notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. notice_mail: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. Außerdem wurden automatisch Rechnungen an die Bestellgruppenmitglieder geschickt. - settings_not_set: Keine Emails mit Rechnungen versendet. Bitte überprüfe die Einstellungen. + settings_not_set: Keine Emails mit Bestellgruppenrechnungen versendet. Bitte überprüfe die Einstellungen. Steuernummer gesetzt? close_all_direct_with_invoice: notice: 'Es wurden %{count} Bestellung abgerechnet.' close_direct: diff --git a/config/locales/en.yml b/config/locales/en.yml index 7b947156..30e864f0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -735,7 +735,7 @@ en: contact_phone: 'Phone: %{contact_phone}' contact_address: 'Adress : %{contact_address}' name: 'Ordergroup: %{ordergroup}' - filename: Invoice %{number} + filename: Invoice%{number} invoicee: Invoicee invoicer: Invoicer invoice_date: 'Invoice date: %{invoice_date}' @@ -810,7 +810,7 @@ en: close: alert: 'An error occured while accounting: %{message}' notice: Order was settled succesfully, the balance of the account was updated. - settings_not_set: No emails with invoices sent. Please check the settings. + settings_not_set: No emails with order group invoices sent. Please check the settings. Tax number set? close_all_direct_with_invoice: notice: '%{count} orders have been settled.' close_direct: From 09679812af4d6a133548855370f314bae7d650ac Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Fri, 24 Dec 2021 13:35:08 +0100 Subject: [PATCH 11/18] configure rubocop_todo to prevent errors --- .rubocop_todo.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7830d550..dda4e7d9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -269,6 +269,8 @@ Lint/Void: # Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: Max: 143 + Exclude: + - 'app/documents/group_order_invoice_pdf.rb' # Offense count: 72 # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. @@ -419,6 +421,7 @@ RSpec/Capybara/FeatureMethods: - 'spec/integration/receive_spec.rb' - 'spec/integration/session_spec.rb' - 'spec/integration/supplier_spec.rb' + - 'spec/integration/group_order_invoices_spec.rb' # Offense count: 27 # Configuration parameters: Prefixes. From bafa163ce50fc7142ea226f7c5aacaa1d81795c7 Mon Sep 17 00:00:00 2001 From: Viehlieb <3feuerba@informatik.uni-hamburg.de> Date: Fri, 24 Dec 2021 13:35:26 +0100 Subject: [PATCH 12/18] fix rubocop errors --- .../group_order_invoices_controller.rb | 10 ++- app/documents/group_order_invoice_pdf.rb | 69 +++++++------- app/mailers/mailer.rb | 1 - app/models/group_order_invoice.rb | 10 +-- config/routes.rb | 1 - spec/factories/group_order_invoice.rb | 2 +- spec/integration/group_order_invoices_spec.rb | 90 +++++++++---------- spec/models/group_order_invoice_spec.rb | 39 ++++---- 8 files changed, 107 insertions(+), 115 deletions(-) diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb index bf6d559f..82c411da 100644 --- a/app/controllers/group_order_invoices_controller.rb +++ b/app/controllers/group_order_invoices_controller.rb @@ -9,9 +9,11 @@ class GroupOrderInvoicesController < ApplicationController send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number] end end - else raise RecordInvalid - redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => "#{error} " + I18n.t('errors.check_tax_number')) + else + raise RecordInvalid end + rescue => error + redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{error} " + I18n.t('errors.check_tax_number')) end def destroy @@ -27,7 +29,7 @@ class GroupOrderInvoicesController < ApplicationController def create go = GroupOrder.find(params[:group_order]) @order = go.order - goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) + GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) respond_to do |format| format.js end @@ -35,4 +37,4 @@ class GroupOrderInvoicesController < ApplicationController rescue => error redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => error) end -end \ No newline at end of file +end diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb index 13856330..76bf107f 100644 --- a/app/documents/group_order_invoice_pdf.rb +++ b/app/documents/group_order_invoice_pdf.rb @@ -12,8 +12,8 @@ class GroupOrderInvoicePdf < RenderPDF ordergroup = @options[:ordergroup] # From paragraph - bounding_box [margin_box.right - 200, margin_box.top-20], width: 200 do - text I18n.t('documents.group_order_invoice_pdf.invoicer') + bounding_box [margin_box.right - 200, margin_box.top - 20], width: 200 do + text I18n.t('documents.group_order_invoice_pdf.invoicer') move_down 7 text FoodsoftConfig[:name], size: fontsize(9), align: :left move_down 5 @@ -32,24 +32,23 @@ class GroupOrderInvoicePdf < RenderPDF text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left end - # Receiving Ordergroup bounding_box [margin_box.left, margin_box.top - 20], width: 200 do - text I18n.t('documents.group_order_invoice_pdf.invoicee') + text I18n.t('documents.group_order_invoice_pdf.invoicee') move_down 7 - text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s ), size: fontsize(9) + text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s), size: fontsize(9) move_down 5 if ordergroup.contact_address - text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s ), size: fontsize(9) + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s), size: fontsize(9) move_down 5 end if ordergroup.contact_phone - text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s ), size: fontsize(9) + text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s), size: fontsize(9) move_down 5 end end - #invoice Date and nnvoice number + # invoice Date and nnvoice number bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left move_down 5 @@ -69,36 +68,37 @@ class GroupOrderInvoicePdf < RenderPDF total_net = 0 # Articles - 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_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 group_order = GroupOrder.find(@options[:group_order].id) marge = FoodsoftConfig[:price_markup] # data table looks different when price_markup > 0 - if marge == 0 - data = [I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')] - else - data = [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] - end - goa_tax_hash = GroupOrderArticle.where(group_order_id: group_order.id).find_each.group_by {|oat| oat.order_article.price.tax} + data = if marge == 0 + [I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')] + else + [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] + 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.each do |tax, group_order_articles| group_order_articles.each do |goa| # if no unit is received, nothing is to be charged next if goa.result.to_i == 0 + order_article = goa.order_article goa_total_net = goa.result * order_article.price.price goa_total_gross = goa.result * order_article.price.gross_price data << [order_article.article.name, - goa.result.to_i, - number_to_currency(order_article.price.price), - number_to_currency(goa_total_net), - tax.to_s + '%', - number_to_currency(goa.total_price)] - tax_hash_net[tax.to_i] += goa_total_net - tax_hash_gross[tax.to_i] += goa_total_gross - total_net += goa_total_net - total_gross += goa_total_gross + goa.result.to_i, + number_to_currency(order_article.price.price), + number_to_currency(goa_total_net), + tax.to_s + '%', + number_to_currency(goa.total_price)] + tax_hash_net[tax.to_i] += goa_total_net + tax_hash_gross[tax.to_i] += goa_total_gross + total_net += goa_total_net + total_gross += goa_total_gross end end @@ -106,7 +106,7 @@ class GroupOrderInvoicePdf < RenderPDF # article information + data table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| table.header = true - table.position= :center + table.position = :center table.cells.border_width = 1 table.cells.border_color = '666666' @@ -118,23 +118,22 @@ class GroupOrderInvoicePdf < RenderPDF sum = [] sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_net'), number_to_currency(total_net)] - tax_hash_net.keys.each do |tax| - sum << [nil,nil,nil,nil, I18n.t('documents.group_order_invoice_pdf.tax_included', tax: tax), number_to_currency(tax_hash_gross[tax] - tax_hash_net[tax])] + tax_hash_net.each_key.each do |tax| + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.tax_included', tax: tax), number_to_currency(tax_hash_gross[tax] - tax_hash_net[tax])] end unless marge == 0 - sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.markup_included', marge: marge), number_to_currency(total_gross * marge/100.0)] + sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.markup_included', marge: marge), number_to_currency(total_gross * marge / 100.0)] end - end_sum = total_gross * (1 + marge/100.0) + end_sum = total_gross * (1 + marge / 100.0) sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(end_sum)] # table for sum - table sum, position: :right, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| + table sum, position: :right, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table| sum.length.times do |count| table.row(count).columns(0..5).borders = [] end - table.row(sum.length-1).columns(0..4).borders = [] - table.row(sum.length-1).border_bottom_width = 2 - table.row(sum.length-1).columns(5).borders = [:bottom] - + table.row(sum.length - 1).columns(0..4).borders = [] + table.row(sum.length - 1).border_bottom_width = 2 + table.row(sum.length - 1).columns(5).borders = [:bottom] end move_down 15 diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index ae99e08c..48367f17 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -63,7 +63,6 @@ class Mailer < ActionMailer::Base subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier) end - # Sends order result for specific Ordergroup def order_result(user, group_order) @order = group_order.order diff --git a/app/models/group_order_invoice.rb b/app/models/group_order_invoice.rb index f39b4b2a..3e3d809b 100644 --- a/app/models/group_order_invoice.rb +++ b/app/models/group_order_invoice.rb @@ -1,5 +1,4 @@ class GroupOrderInvoice < ApplicationRecord - belongs_to :group_order validates_presence_of :group_order validates_uniqueness_of :invoice_number @@ -8,10 +7,10 @@ class GroupOrderInvoice < ApplicationRecord def generate_invoice_number(count) trailing_number = count.to_s.rjust(4, '0') - unless GroupOrderInvoice.find_by(invoice_number: Time.now.strftime("%Y%m%d") + trailing_number) - Time.now.strftime("%Y%m%d") + trailing_number - else + if GroupOrderInvoice.find_by(invoice_number: Time.now.strftime("%Y%m%d") + trailing_number) generate_invoice_number(count.to_i + 1) + else + Time.now.strftime("%Y%m%d") + trailing_number end end @@ -44,7 +43,7 @@ class GroupOrderInvoice < ApplicationRecord invoice_data[:order_articles] = {} group_order.order_articles.each do |order_article| # Get the result of last time ordering, if possible - goa = group_order.group_order_articles.detect { |goa| goa.order_article_id == order_article.id } + goa = group_order.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id } # Build hash with relevant data invoice_data[:order_articles][order_article.id] = { @@ -55,6 +54,5 @@ class GroupOrderInvoice < ApplicationRecord } end invoice_data - end end diff --git a/config/routes.rb b/config/routes.rb index b0f5c300..8b166ea3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -172,7 +172,6 @@ Rails.application.routes.draw do get :unpaid, on: :collection end - resources :links, controller: 'financial_links', only: [:create, :show] do collection do get :incomplete diff --git a/spec/factories/group_order_invoice.rb b/spec/factories/group_order_invoice.rb index 036a2815..89723873 100644 --- a/spec/factories/group_order_invoice.rb +++ b/spec/factories/group_order_invoice.rb @@ -2,6 +2,6 @@ require 'factory_bot' FactoryBot.define do factory :group_order_invoice do - group_order{ create :group_order} + group_order { create :group_order } end end diff --git a/spec/integration/group_order_invoices_spec.rb b/spec/integration/group_order_invoices_spec.rb index 05535d76..f2a76847 100644 --- a/spec/integration/group_order_invoices_spec.rb +++ b/spec/integration/group_order_invoices_spec.rb @@ -3,59 +3,55 @@ require_relative '../spec_helper' feature GroupOrderInvoice, js: true do let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } 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(:oa) { order.order_articles.find_by_article_id(article.id) } let(:ftt) { create :financial_transaction_type } let(:goa) { create :group_order_article, group_order: go, order_article: oa } + include ActiveJob::TestHelper + before { login admin } - describe 'trigger process' do + after { clear_enqueued_jobs } - include ActiveJob::TestHelper - - before { login admin } - after { clear_enqueued_jobs } - - it 'does not enqueue MailerJob when order is settled if tax_number or options not set' do - goa.update_quantities 2, 0 - oa.update_results! - visit confirm_finance_order_path(id: order.id) - click_link_or_button I18n.t('finance.balancing.confirm.clear') - expect(NotifyGroupOrderInvoiceJob).not_to have_been_enqueued - end - - it 'enqueues MailerJob when order is settled if tax_number or options are set' do - goa.update_quantities 2, 0 - oa.update_results! - order.reload - FoodsoftConfig[:group_order_invoices] = { use: true } - FoodsoftConfig[:contact][:tax_number] = 12345678 - visit confirm_finance_order_path(id: order.id, type: ftt) - expect(page).to have_selector(: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 - 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 - goa.update_quantities 2, 0 - oa.update_results! - FoodsoftConfig[:contact][:tax_number] = 12345678 - order.update!(state: 'closed') - order.reload - visit finance_order_index_path - click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate') - expect(GroupOrderInvoice.all.count).to eq(1) - end + it 'does not enqueue MailerJob when order is settled if tax_number or options not set' do + goa.update_quantities 2, 0 + oa.update_results! + visit confirm_finance_order_path(id: order.id) + click_link_or_button I18n.t('finance.balancing.confirm.clear') + expect(NotifyGroupOrderInvoiceJob).not_to have_been_enqueued end -end \ No newline at end of file + + it 'enqueues MailerJob when order is settled if tax_number or options are set' do + goa.update_quantities 2, 0 + oa.update_results! + order.reload + FoodsoftConfig[:group_order_invoices] = { use: true } + FoodsoftConfig[:contact][:tax_number] = 12_345_678 + visit confirm_finance_order_path(id: order.id, type: ftt) + expect(page).to have_selector(: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 + 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 + goa.update_quantities 2, 0 + oa.update_results! + FoodsoftConfig[:contact][:tax_number] = 12_345_678 + order.update!(state: 'closed') + order.reload + visit finance_order_index_path + click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate') + expect(GroupOrderInvoice.all.count).to eq(1) + end +end diff --git a/spec/models/group_order_invoice_spec.rb b/spec/models/group_order_invoice_spec.rb index 1f3fc559..2da767e4 100644 --- a/spec/models/group_order_invoice_spec.rb +++ b/spec/models/group_order_invoice_spec.rb @@ -4,58 +4,57 @@ describe GroupOrderInvoice do let(:user) { create :user, groups: [create(:ordergroup)] } let(:supplier) { create :supplier } let(:article) { create :article, supplier: supplier } - let(:order){ create :order } - let(:group_order) {create :group_order, order: order, ordergroup: user.ordergroup } + let(:order) { create :order } + let(:group_order) { create :group_order, order: order, ordergroup: user.ordergroup } describe 'erroneous group order invoice' do let(:goi) { create :group_order_invoice, group_order_id: group_order.id } it 'does not create group order invoice if tax_number not set' do - expect{goi}.to raise_error(ActiveRecord::RecordInvalid) + expect { goi }.to raise_error(ActiveRecord::RecordInvalid) end - end describe 'valid group order invoice' do before do - FoodsoftConfig[:contact][:tax_number] = 12345678 + FoodsoftConfig[:contact][:tax_number] = 123_457_8 end invoice_number1 = Time.now.strftime("%Y%m%d") + '0001' invoice_number2 = Time.now.strftime("%Y%m%d") + '0002' - let(:user_2) { create :user, groups: [create(:ordergroup)] } + let(:user2) { create :user, groups: [create(:ordergroup)] } - let(:goi_1) { create :group_order_invoice, group_order_id: group_order.id } - let(:goi_2) { create :group_order_invoice, group_order_id: group_order.id } + let(:goi1) { create :group_order_invoice, group_order_id: group_order.id } + let(:goi2) { create :group_order_invoice, group_order_id: group_order.id } - let(:group_order_2) {create :group_order, order: order, ordergroup: user_2.ordergroup } + let(:group_order2) { create :group_order, order: order, ordergroup: user2.ordergroup } - let(:goi_3) { create :group_order_invoice, group_order_id: group_order_2.id } - let(:goi_4) { create :group_order_invoice, group_order_id: group_order_2.id, invoice_number: invoice_number1 } + let(:goi3) { create :group_order_invoice, group_order_id: group_order2.id } + let(:goi4) { create :group_order_invoice, group_order_id: group_order2.id, invoice_number: invoice_number1 } it 'creates group order invoice if tax_number is set' do - expect(goi_1).to be_valid + expect(goi1).to be_valid end it 'sets invoice_number according to date' do number = Time.now.strftime("%Y%m%d") + '0001' - expect(goi_1.invoice_number).to eq(number.to_i) + expect(goi1.invoice_number).to eq(number.to_i) end it 'fails to create if group_order_id is used multiple times for creation' do - expect(goi_1.group_order.id).to eq(group_order.id) - expect{goi_2}.to raise_error(ActiveRecord::RecordNotUnique) + expect(goi1.group_order.id).to eq(group_order.id) + expect { goi2 }.to raise_error(ActiveRecord::RecordNotUnique) end it 'creates two different group order invoice with different invoice_numbers' do - expect(goi_1.invoice_number).to eq(invoice_number1.to_i) - expect(goi_3.invoice_number).to eq(invoice_number2.to_i) + expect(goi1.invoice_number).to eq(invoice_number1.to_i) + expect(goi3.invoice_number).to eq(invoice_number2.to_i) end it 'fails to create two different group order invoice with same invoice_numbers' do - goi_1 - expect{goi_4}.to raise_error(ActiveRecord::RecordInvalid) + goi1 + expect { goi4 }.to raise_error(ActiveRecord::RecordInvalid) end end -end \ No newline at end of file +end From 1de377c13a8818fc78cad79a12c9d108bb7b1265 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 27 Jan 2022 12:42:29 +0100 Subject: [PATCH 13/18] add vat exempt option in payment_tab ~ refs #automatic_go_invoices --- app/views/admin/configs/_tab_payment.html.haml | 4 +++- config/locales/de.yml | 6 ++++-- config/locales/en.yml | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml index de7ea627..47f77b04 100644 --- a/app/views/admin/configs/_tab_payment.html.haml +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -13,9 +13,11 @@ = config_input form, :charge_members_manually, as: :boolean = config_input form, :use_iban, as: :boolean = config_input form, :use_self_service, as: :boolean +%h4= t '.group_order_invoices' = form.fields_for :group_order_invoices do |field| = config_input field, :use, 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' = form.simple_fields_for :order_schedule do |fields| diff --git a/config/locales/de.yml b/config/locales/de.yml index 1512514a..e7641244 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -304,6 +304,7 @@ de: emails_title: E-Mails versenden tab_payment: schedule_title: Bestellschema + group_order_invoices: Bestellgruppenrechnungen tab_security: default_roles_title: Zugriff auf default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche. @@ -670,8 +671,9 @@ de: no_automatic_distribution: Keine automatische Verteilung use_apple_points: Apfelpunkte verwenden group_order_invoices: - use: Bestellgruppen Rechnungen automatisch bei Abrechnung einer Bestellung versenden - payment_method: Zahlungsart für Bestellgruppenrechnungen + use: Automatisch bei Abrechnung per Mail versenden + payment_method: Zahlungsart + vat_exempt: Diese Foodcoop ist MwSt. befreit use_boxfill: Kistenauffüllphase use_iban: IBAN verwenden use_nick: Benutzernamen verwenden diff --git a/config/locales/en.yml b/config/locales/en.yml index 30e864f0..2afc3afe 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -304,6 +304,7 @@ en: emails_title: Sending email tab_payment: schedule_title: Ordering schedule + group_order_invoices: Group order invoices tab_security: default_roles_title: Access to default_roles_paragraph: By default every member of the foodcoop has access to the following areas. @@ -671,8 +672,9 @@ en: no_automatic_distribution: No automatic distribution use_apple_points: Apple points group_order_invoices: - use: Automatically send group order invoices when an order is settled - payment_method: Payment method for group order invoices + use: Send automatically via mail after oder settlement + payment_method: Payment method + vat_exempt: This foodcoopis VAT exempt use_boxfill: Box-fill phase use_iban: Use IBAN use_nick: Use nicknames From bddaebe1a9a51ce0ac01bc79095bcb87629e4d11 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 27 Jan 2022 16:42:22 +0100 Subject: [PATCH 14/18] add option vor vat_exempt inoices ~ refs automatic go invoices --- .../finance/balancing_controller.rb | 2 +- app/documents/group_order_invoice_pdf.rb | 67 ++++++++++++++++++- .../admin/configs/_tab_payment.html.haml | 2 +- config/locales/de.yml | 21 ++++-- config/locales/en.yml | 16 ++++- spec/integration/group_order_invoices_spec.rb | 24 +++---- 6 files changed, 106 insertions(+), 26 deletions(-) diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 485fe7b7..1bd575e7 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -84,7 +84,7 @@ class Finance::BalancingController < Finance::BaseController note = t('finance.balancing.close.notice') if @order.closed? 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| alert = t('finance.balancing.close.settings_not_set') goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb index 76bf107f..f3d93d33 100644 --- a/app/documents/group_order_invoice_pdf.rb +++ b/app/documents/group_order_invoice_pdf.rb @@ -64,6 +64,64 @@ class GroupOrderInvoicePdf < RenderPDF move_down 5 #------------- 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_net = 0 # 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_gross = Hash.new(0) # same here with gross prices - group_order = GroupOrder.find(@options[:group_order].id) marge = FoodsoftConfig[:price_markup] # data table looks different when price_markup > 0 @@ -80,7 +137,7 @@ class GroupOrderInvoicePdf < RenderPDF else [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] 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| group_order_articles.each do |goa| # 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] 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 diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml index 47f77b04..70502c90 100644 --- a/app/views/admin/configs/_tab_payment.html.haml +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -15,7 +15,7 @@ = config_input form, :use_self_service, as: :boolean %h4= t '.group_order_invoices' = 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, :vat_exempt, as: :boolean = config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'} diff --git a/config/locales/de.yml b/config/locales/de.yml index e7641244..d7dc910f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -582,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_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. + 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 homepage: Webseite der Foodcoop ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat. @@ -609,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. 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_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_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. @@ -641,6 +643,10 @@ de: email_from: Absenderadresse email_replyto: Antwortadresse 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 homepage: Webseite ignore_browser_locale: Browsersprache ignorieren @@ -670,10 +676,6 @@ de: first_order_first_serve: Zuerst an die verteilen, die zuerst bestellt haben no_automatic_distribution: Keine automatische Verteilung use_apple_points: Apfelpunkte verwenden - group_order_invoices: - use: Automatisch bei Abrechnung per Mail versenden - payment_method: Zahlungsart - vat_exempt: Diese Foodcoop ist MwSt. befreit use_boxfill: Kistenauffüllphase use_iban: IBAN verwenden use_nick: Benutzernamen verwenden @@ -742,13 +744,20 @@ de: contact_address: 'Adresse : %{contact_address}' name: Bestellgruppe %{ordergroup} payment_method: 'Zahlungsart: %{payment_method}' + sum_to_pay: Zu zahlen gesamt sum_to_pay_net: Zu zahlen gesamt (netto) 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:' tax_excluded: exkl. MwSt. tax_included: zzgl. Gesamtsumme MwSt. %{tax}% tax_number: 'Steuernummer: %{number}' title: Rechnung für die Bestellung bei %{supplier} + vat_exempt_rows: + - Name + - Anzahl + - Einzelpreis + - Artikel Gesamtpreis no_price_markup_rows: - Name - Anzahl diff --git a/config/locales/en.yml b/config/locales/en.yml index 2afc3afe..ef091a49 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -583,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_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. + 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. homepage: Website of your foodcoop. ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet. @@ -672,7 +675,7 @@ en: no_automatic_distribution: No automatic distribution use_apple_points: Apple points group_order_invoices: - use: Send automatically via mail after oder settlement + use_automatic_invoices: Send automatically via mail after oder settlement payment_method: Payment method vat_exempt: This foodcoopis VAT exempt use_boxfill: Box-fill phase @@ -744,6 +747,8 @@ en: invoice_number: 'Invoice number: %{invoice_number}' markup_included: incl Foodcoop Marge on gross price %{marge}% 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_gross: Total sum (gross) table_headline: 'The following items will be charged for the order:' @@ -751,16 +756,21 @@ en: tax_included: incl. VAT %{tax}% tax_number: 'Tax number: %{number}' title: Invoice for order at %{supplier} + vat_exempt_rows: + - Name + - Quantity + - Unit price + - Total price no_price_markup_rows: - Name - - Amount + - Quantity - Unit price (net) - Total price (net) - VAT - Total price (gross) price_markup_rows: - Name - - Amount + - Quantity - Unit price (net) - Total price (net) - VAT diff --git a/spec/integration/group_order_invoices_spec.rb b/spec/integration/group_order_invoices_spec.rb index f2a76847..615bc218 100644 --- a/spec/integration/group_order_invoices_spec.rb +++ b/spec/integration/group_order_invoices_spec.rb @@ -4,7 +4,7 @@ feature GroupOrderInvoice, js: true do let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } 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(: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(:ftt) { create :financial_transaction_type } 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 oa.update_results! order.reload - FoodsoftConfig[:group_order_invoices] = { use: true } + FoodsoftConfig[:group_order_invoices] = { use_automatic_invoices: true } FoodsoftConfig[:contact][:tax_number] = 12_345_678 visit confirm_finance_order_path(id: order.id, type: ftt) expect(page).to have_selector(: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 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 goa.update_quantities 2, 0 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') expect(GroupOrderInvoice.all.count).to eq(1) 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 From 1fc582319f64652b9080d81d40cc3b6bdd648943 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Fri, 28 Jan 2022 13:39:17 +0100 Subject: [PATCH 15/18] fix order.ordergroup nil pointer error ~ automatic go invoices --- app/views/finance/balancing/_orders.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/finance/balancing/_orders.html.haml b/app/views/finance/balancing/_orders.html.haml index addbf930..dddb00c2 100644 --- a/app/views/finance/balancing/_orders.html.haml +++ b/app/views/finance/balancing/_orders.html.haml @@ -21,7 +21,7 @@ %td= show_user(order.updated_by) %td{id: "generate-invoice#{order.id}"} - 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} -else = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set') From 81daddfb10b73875aed906615574456fed5365f8 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 3 Feb 2022 10:56:43 +0100 Subject: [PATCH 16/18] fix nil pointer~ refs automatic go invoice --- app/views/group_order_invoices/_links.html.haml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml index 0d270756..78922d76 100644 --- a/app/views/group_order_invoices/_links.html.haml +++ b/app/views/group_order_invoices/_links.html.haml @@ -1,11 +1,12 @@ -.row - - order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| - .row +- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| + .row + .column.small-3 = 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' + .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 - - 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 - %br + - 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 From bb69c3581a25fea64cb35c9a53333c3e623c58b8 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 3 Feb 2022 11:33:16 +0100 Subject: [PATCH 17/18] rubocop styling ~ refs automatic go invoice --- app/documents/group_order_invoice_pdf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb index f3d93d33..aba39506 100644 --- a/app/documents/group_order_invoice_pdf.rb +++ b/app/documents/group_order_invoice_pdf.rb @@ -66,7 +66,7 @@ class GroupOrderInvoicePdf < RenderPDF #------------- Table Data ----------------------- @group_order = GroupOrder.find(@options[:group_order].id) - if (FoodsoftConfig[:group_order_invoices][:vat_exempt]) + if FoodsoftConfig[:group_order_invoices][:vat_exempt] body_for_vat_exempt else body_with_vat From 81a5194023f27179c08b1f5f16b0e91536ff8189 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 27 Jan 2022 12:42:29 +0100 Subject: [PATCH 18/18] 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 --- .../finance/balancing_controller.rb | 2 +- app/documents/group_order_invoice_pdf.rb | 67 ++++++++++++++++++- .../admin/configs/_tab_payment.html.haml | 6 +- app/views/finance/balancing/_orders.html.haml | 2 +- .../group_order_invoices/_links.html.haml | 15 +++-- config/locales/de.yml | 21 ++++-- config/locales/en.yml | 20 ++++-- spec/integration/group_order_invoices_spec.rb | 24 +++---- 8 files changed, 122 insertions(+), 35 deletions(-) diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 485fe7b7..1bd575e7 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -84,7 +84,7 @@ class Finance::BalancingController < Finance::BaseController note = t('finance.balancing.close.notice') if @order.closed? 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| alert = t('finance.balancing.close.settings_not_set') goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id) diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb index 76bf107f..aba39506 100644 --- a/app/documents/group_order_invoice_pdf.rb +++ b/app/documents/group_order_invoice_pdf.rb @@ -64,6 +64,64 @@ class GroupOrderInvoicePdf < RenderPDF move_down 5 #------------- 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_net = 0 # 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_gross = Hash.new(0) # same here with gross prices - group_order = GroupOrder.find(@options[:group_order].id) marge = FoodsoftConfig[:price_markup] # data table looks different when price_markup > 0 @@ -80,7 +137,7 @@ class GroupOrderInvoicePdf < RenderPDF else [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)] 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| group_order_articles.each do |goa| # 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] 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 diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml index de7ea627..70502c90 100644 --- a/app/views/admin/configs/_tab_payment.html.haml +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -13,9 +13,11 @@ = config_input form, :charge_members_manually, as: :boolean = config_input form, :use_iban, as: :boolean = config_input form, :use_self_service, as: :boolean +%h4= t '.group_order_invoices' = form.fields_for :group_order_invoices do |field| - = config_input field, :use, as: :boolean - = config_input field, :payment_method, as: :string, input_html: {class: 'input-mini'} + = config_input field, :use_automatic_invoices, as: :boolean + = config_input field, :vat_exempt, as: :boolean + = config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'} %h4= t '.schedule_title' = form.simple_fields_for :order_schedule do |fields| diff --git a/app/views/finance/balancing/_orders.html.haml b/app/views/finance/balancing/_orders.html.haml index addbf930..dddb00c2 100644 --- a/app/views/finance/balancing/_orders.html.haml +++ b/app/views/finance/balancing/_orders.html.haml @@ -21,7 +21,7 @@ %td= show_user(order.updated_by) %td{id: "generate-invoice#{order.id}"} - 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} -else = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set') diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml index 0d270756..78922d76 100644 --- a/app/views/group_order_invoices/_links.html.haml +++ b/app/views/group_order_invoices/_links.html.haml @@ -1,11 +1,12 @@ -.row - - order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| - .row +- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go| + .row + .column.small-3 = 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' + .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 - - 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 - %br + - 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 diff --git a/config/locales/de.yml b/config/locales/de.yml index 1512514a..d7dc910f 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -304,6 +304,7 @@ de: emails_title: E-Mails versenden tab_payment: schedule_title: Bestellschema + group_order_invoices: Bestellgruppenrechnungen tab_security: default_roles_title: Zugriff auf 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_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. + 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 homepage: Webseite der Foodcoop 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. 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_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_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. @@ -640,6 +643,10 @@ de: email_from: Absenderadresse email_replyto: Antwortadresse 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 homepage: Webseite ignore_browser_locale: Browsersprache ignorieren @@ -669,9 +676,6 @@ de: first_order_first_serve: Zuerst an die verteilen, die zuerst bestellt haben no_automatic_distribution: Keine automatische Verteilung 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_iban: IBAN verwenden use_nick: Benutzernamen verwenden @@ -740,13 +744,20 @@ de: contact_address: 'Adresse : %{contact_address}' name: Bestellgruppe %{ordergroup} payment_method: 'Zahlungsart: %{payment_method}' + sum_to_pay: Zu zahlen gesamt sum_to_pay_net: Zu zahlen gesamt (netto) 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:' tax_excluded: exkl. MwSt. tax_included: zzgl. Gesamtsumme MwSt. %{tax}% tax_number: 'Steuernummer: %{number}' title: Rechnung für die Bestellung bei %{supplier} + vat_exempt_rows: + - Name + - Anzahl + - Einzelpreis + - Artikel Gesamtpreis no_price_markup_rows: - Name - Anzahl diff --git a/config/locales/en.yml b/config/locales/en.yml index 30e864f0..ef091a49 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -304,6 +304,7 @@ en: emails_title: Sending email tab_payment: schedule_title: Ordering schedule + group_order_invoices: Group order invoices tab_security: default_roles_title: Access to 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_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. + 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. homepage: Website of your foodcoop. 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 use_apple_points: Apple points group_order_invoices: - use: Automatically send group order invoices when an order is settled - payment_method: Payment method for group order invoices + use_automatic_invoices: Send automatically via mail after oder settlement + payment_method: Payment method + vat_exempt: This foodcoopis VAT exempt use_boxfill: Box-fill phase use_iban: Use IBAN use_nick: Use nicknames @@ -742,6 +747,8 @@ en: invoice_number: 'Invoice number: %{invoice_number}' markup_included: incl Foodcoop Marge on gross price %{marge}% 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_gross: Total sum (gross) table_headline: 'The following items will be charged for the order:' @@ -749,16 +756,21 @@ en: tax_included: incl. VAT %{tax}% tax_number: 'Tax number: %{number}' title: Invoice for order at %{supplier} + vat_exempt_rows: + - Name + - Quantity + - Unit price + - Total price no_price_markup_rows: - Name - - Amount + - Quantity - Unit price (net) - Total price (net) - VAT - Total price (gross) price_markup_rows: - Name - - Amount + - Quantity - Unit price (net) - Total price (net) - VAT diff --git a/spec/integration/group_order_invoices_spec.rb b/spec/integration/group_order_invoices_spec.rb index f2a76847..615bc218 100644 --- a/spec/integration/group_order_invoices_spec.rb +++ b/spec/integration/group_order_invoices_spec.rb @@ -4,7 +4,7 @@ feature GroupOrderInvoice, js: true do let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] } 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(: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(:ftt) { create :financial_transaction_type } 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 oa.update_results! order.reload - FoodsoftConfig[:group_order_invoices] = { use: true } + FoodsoftConfig[:group_order_invoices] = { use_automatic_invoices: true } FoodsoftConfig[:contact][:tax_number] = 12_345_678 visit confirm_finance_order_path(id: order.id, type: ftt) expect(page).to have_selector(: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 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 goa.update_quantities 2, 0 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') expect(GroupOrderInvoice.all.count).to eq(1) 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