initial commit
Co-authored-by: viehlieb <pf@pragma-shift.net>
This commit is contained in:
commit
ffe798dc1b
21 changed files with 775 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/.bundle/
|
||||||
|
/doc/
|
||||||
|
/log/*.log
|
||||||
|
/pkg/
|
||||||
|
/tmp/
|
||||||
|
/test/dummy/db/*.sqlite3
|
||||||
|
/test/dummy/db/*.sqlite3-*
|
||||||
|
/test/dummy/log/*.log
|
||||||
|
/test/dummy/storage/
|
||||||
|
/test/dummy/tmp/
|
||||||
10
Gemfile
Normal file
10
Gemfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in group_order_invoice.gemspec.
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem "sqlite3"
|
||||||
|
|
||||||
|
# Start debugger with binding.b [https://github.com/ruby/debug]
|
||||||
|
# gem "debug", ">= 1.0.0"
|
||||||
28
README.md
Normal file
28
README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# GroupOrderInvoice
|
||||||
|
Short description and motivation.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
How to use my plugin.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem "group_order_invoice"
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
```bash
|
||||||
|
$ bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
```bash
|
||||||
|
$ gem install group_order_invoice
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
Contribution directions go here.
|
||||||
|
|
||||||
|
## License
|
||||||
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
||||||
3
Rakefile
Normal file
3
Rakefile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
require "bundler/setup"
|
||||||
|
|
||||||
|
require "bundler/gem_tasks"
|
||||||
13
app/controllers/concerns/send_group_order_invoice_pdf.rb
Normal file
13
app/controllers/concerns/send_group_order_invoice_pdf.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
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] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier])
|
||||||
|
invoice_data[:no_footer] = true
|
||||||
|
pdf = GroupOrderInvoicePdf.new invoice_data
|
||||||
|
send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
|
||||||
|
end
|
||||||
|
end
|
||||||
59
app/controllers/group_order_invoices_controller.rb
Normal file
59
app/controllers/group_order_invoices_controller.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
class GroupOrderInvoicesController < ApplicationController
|
||||||
|
include Concerns::SendGroupOrderInvoicePdf
|
||||||
|
before_action :authenticate_finance
|
||||||
|
|
||||||
|
def show
|
||||||
|
begin
|
||||||
|
@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
|
||||||
|
end
|
||||||
|
rescue ActiveRecord::RecordInvalid => 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
|
||||||
|
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_multiple
|
||||||
|
invoice_date = params[:group_order_invoice][:invoice_date]
|
||||||
|
order_id = params[:group_order_invoice][:order_id]
|
||||||
|
@order = Order.find(order_id)
|
||||||
|
gos = GroupOrder.where("order_id = ?", order_id)
|
||||||
|
gos.each do |go|
|
||||||
|
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
|
||||||
|
goi.invoice_date = invoice_date
|
||||||
|
goi.invoice_number = goi.generate_invoice_number(1)
|
||||||
|
goi.save!
|
||||||
|
end
|
||||||
|
respond_to do |format|
|
||||||
|
format.js
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
go = GroupOrder.find(params[:group_order])
|
||||||
|
@order = go.order
|
||||||
|
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
|
||||||
202
app/documents/group_order_invoice_pdf.rb
Normal file
202
app/documents/group_order_invoice_pdf.rb
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
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 -----------------------
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
marge = FoodsoftConfig[:price_markup]
|
||||||
|
|
||||||
|
# data table looks different when price_markup > 0
|
||||||
|
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
|
||||||
|
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.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)]
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
10
app/jobs/notify_group_order_invoice_job.rb
Normal file
10
app/jobs/notify_group_order_invoice_job.rb
Normal file
|
|
@ -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
|
||||||
58
app/models/group_order_invoice.rb
Normal file
58
app/models/group_order_invoice.rb
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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')
|
||||||
|
if GroupOrderInvoice.find_by(invoice_number: self.invoice_date.strftime("%Y%m%d") + trailing_number)
|
||||||
|
generate_invoice_number(count.to_i + 1)
|
||||||
|
else
|
||||||
|
self.invoice_date.strftime("%Y%m%d") + trailing_number
|
||||||
|
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_date = Time.now unless invoice_date
|
||||||
|
self.invoice_number = generate_invoice_number(1) unless self.invoice_number
|
||||||
|
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 { |tmp_goa| tmp_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
|
||||||
25
app/views/group_order_invoices/_links.html.haml
Normal file
25
app/views/group_order_invoices/_links.html.haml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
.row
|
||||||
|
.column.small-12
|
||||||
|
- show_generate_with_date = true
|
||||||
|
- order.group_orders.each do |go|
|
||||||
|
- if go.group_order_invoice.present?
|
||||||
|
- show_generate_with_date = false
|
||||||
|
- if show_generate_with_date
|
||||||
|
= form_for :group_order_invoice, url: url_for('group_order_invoice#create_multiple'), remote: true do |f|
|
||||||
|
= f.label :invoice_date, I18n.t('activerecord.attributes.group_order_invoice.links.invoice_date')
|
||||||
|
= f.date_field :invoice_date, {value: Date.today, max: Date.today, required: true}
|
||||||
|
= f.hidden_field :order_id, value: order.id
|
||||||
|
= f.submit I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'), class: 'btn btn small'
|
||||||
|
|
||||||
|
- 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
|
||||||
|
.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
|
||||||
|
|
||||||
1
app/views/group_order_invoices/create.js.erb
Normal file
1
app/views/group_order_invoices/create.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||||
1
app/views/group_order_invoices/create_multiple.js.erb
Normal file
1
app/views/group_order_invoices/create_multiple.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||||
1
app/views/group_order_invoices/destroy.js.erb
Normal file
1
app/views/group_order_invoices/destroy.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||||
87
config/locales/de.yml
Normal file
87
config/locales/de.yml
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
de:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
group_order_invoice:
|
||||||
|
name: Bestellgruppenrechnung
|
||||||
|
links:
|
||||||
|
delete: Rechnung löschen
|
||||||
|
download: Rechnung herunterladen
|
||||||
|
generate: Rechnung erzeugen
|
||||||
|
invoice_date: Datum der Bestellgruppenrechnung
|
||||||
|
generate_with_date: setzen & erzeugen
|
||||||
|
|
||||||
|
payment_method: Guthaben
|
||||||
|
tax_number_not_set: Steuernummer in den Einstellungen nicht gesetzt
|
||||||
|
admin:
|
||||||
|
configs:
|
||||||
|
tab_payment:
|
||||||
|
group_order_invoices: Bestellgruppenrechnungen
|
||||||
|
config:
|
||||||
|
hints:
|
||||||
|
group_order_invoices:
|
||||||
|
use_automatic_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)
|
||||||
|
keys:
|
||||||
|
group_order_invoices:
|
||||||
|
use_automatic_invoices: Automatisch bei Abrechnung per Mail versenden
|
||||||
|
payment_method: Zahlungsart
|
||||||
|
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: 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
|
||||||
|
- 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}%
|
||||||
|
finance:
|
||||||
|
balancing:
|
||||||
|
close:
|
||||||
|
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 Bestellgruppenrechnungen versendet. Bitte überprüfe die Einstellungen. Steuernummer gesetzt?
|
||||||
|
mailer:
|
||||||
|
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}
|
||||||
|
orders:
|
||||||
|
index:
|
||||||
|
not_closed: Bestellung noch nicht abgerechnet
|
||||||
86
config/locales/en.yml
Normal file
86
config/locales/en.yml
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
group_order_invoice:
|
||||||
|
name: Group order invoice
|
||||||
|
links:
|
||||||
|
delete: delete invoice
|
||||||
|
download: download invoice
|
||||||
|
invoice_date: date of group order invoice
|
||||||
|
generate: generate invoice
|
||||||
|
generate_with_date: set & generate
|
||||||
|
payment_method: Credit
|
||||||
|
tax_number_not_set: Tax number not set in configs
|
||||||
|
admin:
|
||||||
|
configs:
|
||||||
|
tab_payment:
|
||||||
|
group_order_invoices: Group order invoices
|
||||||
|
config:
|
||||||
|
hints:
|
||||||
|
group_order_invoices:
|
||||||
|
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).
|
||||||
|
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)
|
||||||
|
keys:
|
||||||
|
group_order_invoices:
|
||||||
|
use_automatic_invoices: Send automatically via mail after oder settlement
|
||||||
|
payment_method: Payment method
|
||||||
|
vat_exempt: This foodcoopis VAT exempt
|
||||||
|
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}'
|
||||||
|
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:'
|
||||||
|
tax_excluded: excl. MwSt.
|
||||||
|
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
|
||||||
|
- Quantity
|
||||||
|
- Unit price (net)
|
||||||
|
- Total price (net)
|
||||||
|
- VAT
|
||||||
|
- Total price (gross)
|
||||||
|
price_markup_rows:
|
||||||
|
- Name
|
||||||
|
- Quantity
|
||||||
|
- Unit price (net)
|
||||||
|
- Total price (net)
|
||||||
|
- VAT
|
||||||
|
- Total price (gross) incl. foodcoop margin
|
||||||
|
finance:
|
||||||
|
balancing:
|
||||||
|
close:
|
||||||
|
settings_not_set: No emails with order group invoices sent. Please check the settings. Tax number set?
|
||||||
|
mailer:
|
||||||
|
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}
|
||||||
|
orders:
|
||||||
|
index:
|
||||||
|
not_closed: Order not yet settled
|
||||||
6
config/routes.rb
Normal file
6
config/routes.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
scope '/:foodcoop' do
|
||||||
|
post 'finance/group_order_invoice', to: 'group_order_invoices#create_multiple'
|
||||||
|
resources :group_order_invoices
|
||||||
|
end
|
||||||
|
end
|
||||||
13
db/migrate/20211208142719_create_group_order_invoices.rb
Normal file
13
db/migrate/20211208142719_create_group_order_invoices.rb
Normal file
|
|
@ -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
|
||||||
25
group_order_invoice.gemspec
Normal file
25
group_order_invoice.gemspec
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
require_relative "lib/group_order_invoice/version"
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "group_order_invoice"
|
||||||
|
spec.version = GroupOrderInvoice::VERSION
|
||||||
|
spec.authors = [""]
|
||||||
|
spec.email = [""]
|
||||||
|
spec.homepage = "TODO"
|
||||||
|
spec.summary = "TODO: Summary of GroupOrderInvoice."
|
||||||
|
spec.description = "TODO: Description of GroupOrderInvoice."
|
||||||
|
|
||||||
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host"
|
||||||
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
||||||
|
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
||||||
|
|
||||||
|
spec.metadata["homepage_uri"] = spec.homepage
|
||||||
|
spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
||||||
|
spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
||||||
|
|
||||||
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
||||||
|
Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
|
||||||
|
end
|
||||||
|
|
||||||
|
spec.add_dependency "rails", ">= 7.0.4"
|
||||||
|
end
|
||||||
7
spec/factories/group_order_invoice.rb
Normal file
7
spec/factories/group_order_invoice.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
require 'factory_bot'
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :group_order_invoice do
|
||||||
|
group_order { create :group_order }
|
||||||
|
end
|
||||||
|
end
|
||||||
71
spec/integration/group_order_invoices_spec.rb
Normal file
71
spec/integration/group_order_invoices_spec.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
require_relative '../spec_helper'
|
||||||
|
|
||||||
|
feature GroupOrderInvoice, js: true do
|
||||||
|
let(:admin) { create :user, groups: [create(:workgroup, role_finance: true)] }
|
||||||
|
let(:user) { create :user, groups: [create(:ordergroup)] }
|
||||||
|
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, ordergroup: user.ordergroup}
|
||||||
|
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 }
|
||||||
|
|
||||||
|
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_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 '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
|
||||||
|
expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate'))
|
||||||
|
click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate')
|
||||||
|
expect(GroupOrderInvoice.all.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'generates multiple Group Order Invoice for order 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
|
||||||
|
expect(page).to have_selector(:link_or_button, I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'))
|
||||||
|
click_link_or_button I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date')
|
||||||
|
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
|
||||||
59
spec/models/group_order_invoice_spec.rb
Normal file
59
spec/models/group_order_invoice_spec.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
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] = 123_457_8
|
||||||
|
end
|
||||||
|
|
||||||
|
invoice_number1 = Time.now.strftime("%Y%m%d") + '0001'
|
||||||
|
invoice_number2 = Time.now.strftime("%Y%m%d") + '0002'
|
||||||
|
|
||||||
|
let(:user2) { create :user, groups: [create(:ordergroup)] }
|
||||||
|
|
||||||
|
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_order2) { create :group_order, order: order, ordergroup: user2.ordergroup }
|
||||||
|
|
||||||
|
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(goi1).to be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets invoice_number according to date' do
|
||||||
|
number = Time.now.strftime("%Y%m%d") + '0001'
|
||||||
|
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(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(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
|
||||||
|
goi1
|
||||||
|
expect { goi4 }.to raise_error(ActiveRecord::RecordInvalid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue